diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fafebca7..1066505c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,10 +18,10 @@ jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: ${{ github.repository == 'stainless-sdks/kernel-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
@@ -44,9 +44,9 @@ jobs:
permissions:
contents: read
id-token: write
- runs-on: ${{ github.repository == 'stainless-sdks/kernel-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
@@ -67,7 +67,7 @@ jobs:
github.repository == 'stainless-sdks/kernel-python' &&
!startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
- uses: actions/github-script@v8
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: core.setOutput('github_token', await core.getIDToken());
@@ -84,10 +84,10 @@ jobs:
test:
timeout-minutes: 10
name: test
- runs-on: ${{ github.repository == 'stainless-sdks/kernel-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 2e95b5a9..2e7e7190 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 48941b69..057d4a7f 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'kernel/kernel-python-sdk' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check release environment
run: |
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 00000000..4737ca42
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -0,0 +1,102 @@
+name: Release Please
+
+# `next` accumulates validated SDK changes in one versioned PR to `main`.
+# Merging that PR creates the GitHub release; the package publishing workflow
+# runs from the release event.
+on:
+ push:
+ branches:
+ - next
+ - main
+
+permissions:
+ contents: read
+
+concurrency:
+ group: release-please
+ cancel-in-progress: false
+
+jobs:
+ release-please:
+ if: github.repository == 'kernel/kernel-python-sdk'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Mint release token
+ id: release-token
+ uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
+ with:
+ app-id: ${{ secrets.ADMIN_APP_ID }}
+ private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
+ owner: kernel
+ repositories: kernel-python-sdk
+ permission-contents: write
+ permission-issues: write
+ permission-pull-requests: write
+ permission-workflows: write
+
+ - name: Set up Node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: '18.20.2'
+
+ - name: Set up pnpm
+ uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
+ with:
+ version: '9.11.0'
+ run_install: false
+
+ - name: Build pinned release tooling
+ id: tooling
+ env:
+ RELEASE_PLEASE_DIR: ${{ runner.temp }}/release-please
+ RELEASE_PLEASE_SHA: a116e1e520e0f87824acf46a2e79c91d41e819d7
+ run: |
+ set -euo pipefail
+ rm -rf "$RELEASE_PLEASE_DIR"
+ git init "$RELEASE_PLEASE_DIR"
+ git -C "$RELEASE_PLEASE_DIR" remote add origin https://github.com/stainless-api/release-please.git
+ git -C "$RELEASE_PLEASE_DIR" fetch --depth=1 origin "$RELEASE_PLEASE_SHA"
+ git -C "$RELEASE_PLEASE_DIR" checkout --detach FETCH_HEAD
+ pnpm --dir "$RELEASE_PLEASE_DIR" install --frozen-lockfile
+ pnpm --dir "$RELEASE_PLEASE_DIR" build
+ echo "cli=$RELEASE_PLEASE_DIR/build/src/bin/release-please.js" >> "$GITHUB_OUTPUT"
+
+ - name: Open or update the release PR
+ if: github.ref_name == 'next'
+ env:
+ GH_TOKEN: ${{ steps.release-token.outputs.token }}
+ RELEASE_PLEASE: ${{ steps.tooling.outputs.cli }}
+ run: |
+ set -euo pipefail
+ node "$RELEASE_PLEASE" release-pr \
+ --repo-url "$GITHUB_REPOSITORY" \
+ --token "$GH_TOKEN" \
+ --target-branch main \
+ --changes-branch next
+
+ - name: Remove the legacy promotion PR
+ if: github.ref_name == 'next'
+ env:
+ GH_TOKEN: ${{ steps.release-token.outputs.token }}
+ run: |
+ set -euo pipefail
+ legacy=$(gh pr list --repo "$GITHUB_REPOSITORY" --head stainless/release \
+ --state open --json number --jq '.[].number')
+ for pr in $legacy; do
+ gh pr close "$pr" --repo "$GITHUB_REPOSITORY" \
+ --comment "Superseded by the versioned release PR from next to main."
+ done
+ gh api -X DELETE "repos/$GITHUB_REPOSITORY/git/refs/heads/stainless/release" >/dev/null 2>&1 || true
+
+ - name: Create the GitHub release
+ if: github.ref_name == 'main'
+ env:
+ GH_TOKEN: ${{ steps.release-token.outputs.token }}
+ RELEASE_PLEASE: ${{ steps.tooling.outputs.cli }}
+ run: |
+ set -euo pipefail
+ node "$RELEASE_PLEASE" github-release \
+ --repo-url "$GITHUB_REPOSITORY" \
+ --token "$GH_TOKEN" \
+ --target-branch main
diff --git a/.github/workflows/stlc-promote.yml b/.github/workflows/stlc-promote.yml
new file mode 100644
index 00000000..3bcd88c7
--- /dev/null
+++ b/.github/workflows/stlc-promote.yml
@@ -0,0 +1,133 @@
+name: Promote SDK changes
+
+# Staging is the generator's integration history. Production `next` is the
+# developer-facing queue for the next release. This workflow combines the
+# latest released state with validated staging changes, then advances `next`.
+# Release automation maintains the single versioned PR from `next` to `main`.
+on:
+ push:
+ branches: [main]
+ workflow_dispatch: {}
+
+permissions:
+ contents: read
+
+jobs:
+ promote:
+ if: github.repository == 'kernel/kernel-python-sdk-staging'
+ runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }}
+ concurrency:
+ group: stlc-promote
+ cancel-in-progress: true
+ steps:
+ - name: Check out staging
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Mint production token
+ id: production-token
+ uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
+ with:
+ app-id: ${{ secrets.ADMIN_APP_ID }}
+ private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
+ owner: kernel
+ repositories: kernel-python-sdk
+ permission-contents: write
+ permission-pull-requests: write
+ permission-workflows: write
+
+ - name: Fetch production branches
+ id: production
+ env:
+ GH_TOKEN: ${{ steps.production-token.outputs.token }}
+ PRODUCTION_REPO: kernel/kernel-python-sdk
+ run: |
+ set -euo pipefail
+ git remote add production \
+ "https://x-access-token:${GH_TOKEN}@github.com/${PRODUCTION_REPO}.git"
+ git fetch production main
+ if git ls-remote --exit-code --heads production next >/dev/null 2>&1; then
+ git fetch production next
+ echo "has_next=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "has_next=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Prepare the next release branch
+ env:
+ APP_SLUG: ${{ steps.production-token.outputs.app-slug }}
+ GH_TOKEN: ${{ steps.production-token.outputs.token }}
+ HAS_NEXT: ${{ steps.production.outputs.has_next }}
+ PRODUCTION_REPO: kernel/kernel-python-sdk
+ run: |
+ set -euo pipefail
+ bot_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
+ git config user.name "${APP_SLUG}[bot]"
+ git config user.email "${bot_id}+${APP_SLUG}[bot]@users.noreply.github.com"
+
+ open_conflict_pr() {
+ source_ref=$1
+ source_name=$2
+ advance_next=$3
+ conflict_branch=stlc/promotion-conflict
+
+ git merge --abort
+ existing=$(gh pr list --repo "$PRODUCTION_REPO" --base next \
+ --head "$conflict_branch" --state open --json url --jq '.[0].url // ""')
+ if [ -n "$existing" ]; then
+ echo "::error title=SDK promotion blocked::Resolve the existing recovery PR: $existing"
+ exit 1
+ fi
+
+ if [ "$advance_next" = "true" ]; then
+ git push production HEAD:refs/heads/next
+ fi
+ git push production "$source_ref:refs/heads/$conflict_branch" --force
+
+ body=$(mktemp)
+ printf '%s\n' \
+ '## SDK promotion conflict' \
+ '' \
+ "The automated promotion could not merge $source_name into the pending next release." \
+ '' \
+ 'Resolve the conflicts on this branch, validate the SDK, mark this PR ready, and merge it with a merge commit.' \
+ '' \
+ 'After merging, rerun the staging Promote SDK changes workflow to include any newer generated changes.' \
+ > "$body"
+ recovery_url=$(gh pr create --repo "$PRODUCTION_REPO" --draft \
+ --base next --head "$conflict_branch" \
+ --title 'chore: resolve SDK promotion conflict' --body-file "$body")
+ echo "::error title=SDK promotion conflict::Resolve the recovery PR: $recovery_url"
+ exit 1
+ }
+
+ if [ "$HAS_NEXT" = "true" ]; then
+ git checkout -B stlc/promote-next production/next
+ else
+ git checkout -B stlc/promote-next production/main
+ fi
+
+ if ! git merge-base --is-ancestor production/main HEAD; then
+ if ! git merge --no-edit production/main; then
+ open_conflict_pr production/main 'production main' false
+ fi
+ fi
+ if ! git merge-base --is-ancestor origin/main HEAD; then
+ if ! git merge --no-edit origin/main; then
+ open_conflict_pr origin/main 'validated staging changes' true
+ fi
+ fi
+
+ if [ "$HAS_NEXT" = "true" ]; then
+ git merge-base --is-ancestor production/next HEAD
+ fi
+
+ - name: Update the pending release
+ env:
+ GH_TOKEN: ${{ steps.production-token.outputs.token }}
+ run: |
+ set -euo pipefail
+ git push production HEAD:refs/heads/next
+ echo "Updated production next; the versioned release PR will be opened or refreshed."
diff --git a/.github/workflows/stlc-sync.yml b/.github/workflows/stlc-sync.yml
new file mode 100644
index 00000000..216712f2
--- /dev/null
+++ b/.github/workflows/stlc-sync.yml
@@ -0,0 +1,107 @@
+name: Sync SDK repos
+
+# Keeps production and staging on one fast-forward-only history. Optional
+# dispatch tokens make the polling loop eager; the scheduled back-sync is the
+# safety backstop when those secrets are absent.
+on:
+ schedule:
+ - cron: '7,37 * * * *'
+ workflow_dispatch: {}
+ repository_dispatch:
+ types: [prod-released]
+ release:
+ types: [published]
+ push:
+ branches: [main]
+
+jobs:
+ back-sync:
+ if: >-
+ github.repository == 'kernel/kernel-python-sdk-staging' &&
+ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch')
+ runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }}
+ permissions:
+ contents: write
+ concurrency:
+ group: stlc-back-sync
+ cancel-in-progress: true
+ steps:
+ - name: Check out staging
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+
+ - name: Fetch production main
+ run: |
+ git remote add production "https://github.com/kernel/kernel-python-sdk.git"
+ git -c "http.https://github.com/.extraheader=" fetch production main
+
+ - name: Check whether production has content staging lacks
+ id: diff
+ run: |
+ MERGED=$(git merge-tree --write-tree origin/main production/main) || MERGED=conflict
+ STAGING_TREE=$(git rev-parse 'origin/main^{tree}')
+ if [ "$MERGED" = "$STAGING_TREE" ]; then
+ echo "Staging already has production's content. Nothing to pull back."
+ echo "behind=false" >> "$GITHUB_OUTPUT"
+ else
+ echo "behind=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Sync production to staging
+ if: steps.diff.outputs.behind == 'true'
+ run: |
+ if ! git merge-base --is-ancestor origin/main production/main; then
+ echo "::error title=Back-sync blocked::staging main is not an ancestor of production/main."
+ exit 1
+ fi
+ git push origin production/main:refs/heads/main
+
+ notify-back-sync:
+ if: >-
+ github.repository == 'kernel/kernel-python-sdk' &&
+ (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
+ runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }}
+ permissions:
+ contents: read
+ steps:
+ - name: Dispatch back-sync to staging
+ env:
+ DISPATCH_TOKEN: ${{ secrets.STAGING_DISPATCH_TOKEN }}
+ REF_NAME: ${{ github.ref_name }}
+ run: |
+ set -euo pipefail
+ if [ -z "${DISPATCH_TOKEN:-}" ]; then
+ echo "::notice::STAGING_DISPATCH_TOKEN not configured; the scheduled back-sync remains active."
+ exit 0
+ fi
+ payload=$(jq -n --arg ref "$REF_NAME" '{event_type:"prod-released",client_payload:{ref:$ref}}')
+ curl --fail-with-body -sS -X POST -H "Authorization: Bearer $DISPATCH_TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/kernel/kernel-python-sdk-staging/dispatches" -d "$payload"
+
+ seal-dispatch:
+ if: github.repository == 'kernel/kernel-python-sdk-staging' && github.event_name == 'push'
+ runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }}
+ permissions:
+ contents: read
+ concurrency:
+ group: seal-dispatch-${{ github.ref }}
+ cancel-in-progress: false
+ steps:
+ - name: Dispatch tracking sync
+ env:
+ DISPATCH_TOKEN: ${{ secrets.CONFIG_DISPATCH_TOKEN }}
+ HEAD_MSG: ${{ github.event.head_commit.message }}
+ HEAD_AUTHOR_NAME: ${{ github.event.head_commit.author.name }}
+ SHA: ${{ github.sha }}
+ run: |
+ set -euo pipefail
+ if printf '%s' "$HEAD_MSG" | grep -q 'Stainless-Generated-From' || [ "$HEAD_AUTHOR_NAME" = "stlc-bot" ]; then
+ echo "Generated commit; skipping tracking dispatch."
+ exit 0
+ fi
+ if [ -z "${DISPATCH_TOKEN:-}" ]; then
+ echo "::notice::CONFIG_DISPATCH_TOKEN not configured; the config repo's scheduled sync remains active."
+ exit 0
+ fi
+ payload=$(jq -n --arg sha "$SHA" --arg repo "${{ github.repository }}" '{event_type:"seal-custom-code",client_payload:{target:"all",sha:$sha,repo:$repo}}')
+ curl --fail-with-body -sS -X POST -H "Authorization: Bearer $DISPATCH_TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/kernel/kernel/dispatches" -d "$payload"
diff --git a/.python-version b/.python-version
index 43077b24..251b3507 100644
--- a/.python-version
+++ b/.python-version
@@ -1 +1 @@
-3.9.18
+3.10.16
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index c3e01e1e..f118d043 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.53.0"
+ ".": "0.128.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 7915e042..caa7c062 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1 @@
-configured_endpoints: 112
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-a33e59aa1758ba51f13538838ecd70b0a23ed69739b3022e8c2ce0622e42b904.yml
-openapi_spec_hash: c042d2f6880c927be09aa9fa79d7241e
-config_hash: 08d55086449943a8fec212b870061a3f
+configured_endpoints: 175
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 122a4e88..4193ab9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,751 @@
# 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)
+
+
+### Features
+
+* Add atomic preconditions to pool network updates ([9869c91](https://github.com/kernel/kernel-python-sdk/commit/9869c911414a8be186a1a64682487fd38423fb92))
+* Add passkey credential fields and PATCH field add/remove ([84a69ad](https://github.com/kernel/kernel-python-sdk/commit/84a69ad74c46ddcdd87acd88a23dd5330adf555b))
+* Allow replacing vault cards whose provider outcome is unknown ([30f88e1](https://github.com/kernel/kernel-python-sdk/commit/30f88e18b045f3fedbe1bd79d3125db105dd725f))
+* chore(stlc): seal custom-code tracking files ([5a62113](https://github.com/kernel/kernel-python-sdk/commit/5a621135bad2661c79068027773ff32b4e14e0c9))
+* Classify text captchas as token tasks and Binance as a provider ([84f71b8](https://github.com/kernel/kernel-python-sdk/commit/84f71b89f84dea25464cbc22ed92786d73083419))
+* Deliver Kernel card details through fill ([eed3e7b](https://github.com/kernel/kernel-python-sdk/commit/eed3e7ba06c46d3cfa6048d9f02c3d8fff6046e6))
+* Raise Kernel card amount cap to $10,000 ([483f331](https://github.com/kernel/kernel-python-sdk/commit/483f3312824d07e5f315afe0ce5058d4fc4a6e99))
+* Remove Link expires_at, return 400 for missing credential values, and log fill failures ([766dd6e](https://github.com/kernel/kernel-python-sdk/commit/766dd6e961fd36f580540a535cad76e659ecafee))
+
+## [0.126.0](https://github.com/kernel/kernel-python-sdk/compare/v0.125.0...v0.126.0) (2026-10-14)
+
+
+### Features
+
+* support Link Pay Tokens when authorizing Link cards at checkout ([f081364](https://github.com/kernel/kernel-python-sdk/commit/f08136407428a84fd709d491d38694db026f70c8))
+
+## [0.125.0](https://github.com/kernel/kernel-python-sdk/compare/v0.124.0...v0.125.0) (2026-10-13)
+
+
+### Features
+
+* chore(stlc): seal custom-code tracking files ([a77f648](https://github.com/kernel/kernel-python-sdk/commit/a77f648fb68c7b4e6f93826238e9ac21ab39a4dd))
+
+## [0.124.0](https://github.com/kernel/kernel-python-sdk/compare/v0.123.0...v0.124.0) (2026-10-13)
+
+
+### Features
+
+* Accept intent creation without a fixed status ([e6e016a](https://github.com/kernel/kernel-python-sdk/commit/e6e016a5cea493c505b16a05cb8ad2fa6ed9f13b))
+* Complete Visa vault verification and transaction reporting ([fc4b0de](https://github.com/kernel/kernel-python-sdk/commit/fc4b0de79e3242361ef7b11acbd2cbaeb058265e))
+
+
+### Bug Fixes
+
+* seal browser pool acquire SDK results ([4d81af2](https://github.com/kernel/kernel-python-sdk/commit/4d81af2b6103571b59cd1bb520eb85e28488b51c))
+
+## [0.123.0](https://github.com/kernel/kernel-python-sdk/compare/v0.122.0...v0.123.0) (2026-10-09)
+
+
+### Features
+
+* Add video_memory to request the 4GiB VRAM GPU browser tier ([31b7bda](https://github.com/kernel/kernel-python-sdk/commit/31b7bda620f53bc6026b5eb3fac34d50859fa1df))
+* Filter the telemetry SSE stream by event type ([f7daa04](https://github.com/kernel/kernel-python-sdk/commit/f7daa043513d08f6cc6aefec7b058d5bd57f2590))
+* Fix 1Password fill classification for repeat fills and unconfirmed submits ([d387aab](https://github.com/kernel/kernel-python-sdk/commit/d387aab4847329eb4405318a20ee93a372399f70))
+* Support egress allowlists on browser pools ([541fb3f](https://github.com/kernel/kernel-python-sdk/commit/541fb3fc033b8b9542838a116684578cab18fba0))
+
+## [0.122.0](https://github.com/kernel/kernel-python-sdk/compare/v0.121.0...v0.122.0) (2026-10-09)
+
+
+### Features
+
+* Accept client-encrypted credential values in vault items ([a289cba](https://github.com/kernel/kernel-python-sdk/commit/a289cba652251b6f42b60aad92619627efc9304b))
+* Add feature-gated Korean ISP proxies ([56785ca](https://github.com/kernel/kernel-python-sdk/commit/56785cad65cda7e757258204de47f1a4a5974088))
+* Describe vault fill as safe to retry ([e419d6d](https://github.com/kernel/kernel-python-sdk/commit/e419d6d8970f0e78f91b4771969787bb835a6acf))
+* Infer a challenge result for unobserved captcha providers in the relay ([0e4734c](https://github.com/kernel/kernel-python-sdk/commit/0e4734caedae1068b5f3f9eeb5212871dfc4a0ec))
+* Support proxy routes in browser pools ([669ea52](https://github.com/kernel/kernel-python-sdk/commit/669ea5294ab1da43e5042209992364ca009f7053))
+
+## [0.121.0](https://github.com/kernel/kernel-python-sdk/compare/v0.120.0...v0.121.0) (2026-10-07)
+
+
+### Features
+
+* Create and poll 1Password access requests over the 1Password API ([c59f487](https://github.com/kernel/kernel-python-sdk/commit/c59f4871e5dfafd294d9fe9338555d07a6ecd491))
+* Deprecate datacenter proxies in the API ([c34882c](https://github.com/kernel/kernel-python-sdk/commit/c34882c77d247ace0fa23d37da1c3574d21bd1b0))
+
+## [0.120.0](https://github.com/kernel/kernel-python-sdk/compare/v0.119.0...v0.120.0) (2026-10-06)
+
+
+### Features
+
+* Accept network.allowed_hosts on browser create behind an org flag ([3178829](https://github.com/kernel/kernel-python-sdk/commit/3178829222a373a69fdb7617abcab02f409d2d6e))
+* Add us-west placement region support ([915eb0d](https://github.com/kernel/kernel-python-sdk/commit/915eb0d4c9fc70df904515df6f3e07ce6c1ac2c0))
+* Add Visa passkey approval for VGS purchases ([fadc7c9](https://github.com/kernel/kernel-python-sdk/commit/fadc7c95c7fe45df163e6f186384a7d534c8c014))
+* Connect Kernel wallets once the card is stored, with best-effort network tokens ([da49f00](https://github.com/kernel/kernel-python-sdk/commit/da49f007e5b00cdca00f08500db43bfc6332c4c3))
+* Enable Exa highlights and default Perplexity to medium context ([761b766](https://github.com/kernel/kernel-python-sdk/commit/761b766f80e9ccfabf65a412bf777e5a861b400b))
+* Expose named Playwright executors on the browser API ([8c80fc1](https://github.com/kernel/kernel-python-sdk/commit/8c80fc14441a149e4c6b5cd5e7a9a7a1db4706c2))
+
+## [0.119.0](https://github.com/kernel/kernel-python-sdk/compare/v0.118.0...v0.119.0) (2026-10-02)
+
+
+### Features
+
+* Add managed auth provider to vault credential items ([795d824](https://github.com/kernel/kernel-python-sdk/commit/795d8241dfd22dc7667d7fd74c70182cc7467928))
+
+## [0.118.0](https://github.com/kernel/kernel-python-sdk/compare/v0.117.0...v0.118.0) (2026-10-02)
+
+
+### Features
+
+* Add a query filter to the vault list endpoint ([eaa17a1](https://github.com/kernel/kernel-python-sdk/commit/eaa17a1e7068bb70abe9f07f4bba1c818ddeeba1))
+* Add Kernel wallets backed by VGS agentic network tokens, with hosted card capture ([04d839e](https://github.com/kernel/kernel-python-sdk/commit/04d839e6bb948049d3683380efeaae5a3732c959))
+
+## [0.117.0](https://github.com/kernel/kernel-python-sdk/compare/v0.116.0...v0.117.0) (2026-10-02)
+
+
+### Features
+
+* Invoke WebMCP tools with vault item fields ([a75eb77](https://github.com/kernel/kernel-python-sdk/commit/a75eb77bb3722e82b45836e1b9f5f6fc43dfa4ff))
+
+## [0.116.0](https://github.com/kernel/kernel-python-sdk/compare/v0.115.0...v0.116.0) (2026-10-01)
+
+
+### Features
+
+* chore(stlc): seal custom-code tracking files ([c053ce4](https://github.com/kernel/kernel-python-sdk/commit/c053ce45e5d6bfaa4577310d28b3ecf0a7c16f4e))
+
+## [0.115.0](https://github.com/kernel/kernel-python-sdk/compare/v0.114.0...v0.115.0) (2026-09-30)
+
+
+### Features
+
+* Fetch content for selected results from retained searches ([kernel/kernel#3978](https://github.com/kernel/kernel/pull/3978))
+
+## [0.114.0](https://github.com/kernel/kernel-python-sdk/compare/v0.113.0...v0.114.0) (2026-09-30)
+
+
+### Features
+
+* Accept telemetry.storage and settle it at browser create ([28fb2d3](https://github.com/kernel/kernel-python-sdk/commit/28fb2d3c8fd83090d83404a0b407557585bf93f6))
+* Allow export-only network and console telemetry for BAA orgs ([2ff8d4a](https://github.com/kernel/kernel-python-sdk/commit/2ff8d4acacb1b8152b9c7d1b38067c1b02167f40))
+* Expose missing managed auth check URL as verification unavailable ([74dbad8](https://github.com/kernel/kernel-python-sdk/commit/74dbad8f0cdf5b414022ad907f6e20ee54893c59))
+* Let Vaults fill credentials from 1Password ([2937b10](https://github.com/kernel/kernel-python-sdk/commit/2937b10cce135282b6badc7e5bf71d339500ffdb))
+* Send Stripe publishable key when refreshing customer-owned Link grants ([5c1bed3](https://github.com/kernel/kernel-python-sdk/commit/5c1bed353eb421bff589bcc86b2940ca750e3999))
+
+## [0.113.0](https://github.com/kernel/kernel-python-sdk/compare/v0.112.0...v0.113.0) (2026-09-27)
+
+
+### Features
+
+* Randomize automatic search provider routing ([1da4b21](https://github.com/kernel/kernel-python-sdk/commit/1da4b212644549666e19f23146b8d1e2ab9511d9))
+* Support 16GiB memory in browser pools ([7d004de](https://github.com/kernel/kernel-python-sdk/commit/7d004def693a57505b4294307b272ba1fc4f78b2))
+
+## [0.112.0](https://github.com/kernel/kernel-python-sdk/compare/v0.111.0...v0.112.0) (2026-09-24)
+
+
+### Features
+
+* Add profiles to browser pool acquire ([f5c540c](https://github.com/kernel/kernel-python-sdk/commit/f5c540cdd1b0cee422cf47825dbc2212a5b6532f))
+* Correct monitor_disconnected description on computed state ([2b637f5](https://github.com/kernel/kernel-python-sdk/commit/2b637f562b34ad0963b8ba4d0ecca73fa1dd6349))
+* Expose Search rollout access in org entitlements ([cbbbe4c](https://github.com/kernel/kernel-python-sdk/commit/cbbbe4c865eb72c10be0f95e472921cf61898abc))
+* Filter archived telemetry events by type ([1fdfce2](https://github.com/kernel/kernel-python-sdk/commit/1fdfce2a3b05c5af330c93cd8e37a217f4d0f99b))
+* Proxy custom WebMCP tools in the control plane ([179dcd9](https://github.com/kernel/kernel-python-sdk/commit/179dcd9f2c9c27a46bc5f37d241da0f0626ccf82))
+* Publish iframe and worker browser target types ([848805a](https://github.com/kernel/kernel-python-sdk/commit/848805abcfa585e4299742cd8f7a3ff0306febfe))
+* Route browser destinations through selected proxies ([5a0a461](https://github.com/kernel/kernel-python-sdk/commit/5a0a461f25cbb59b43b2695fea8294357f3655fb))
+
+## [0.111.0](https://github.com/kernel/kernel-python-sdk/compare/v0.110.0...v0.111.0) (2026-09-22)
+
+
+### Features
+
+* Allow managed auth logins to disable learned skills ([e230ce3](https://github.com/kernel/kernel-python-sdk/commit/e230ce3a684ce7723256170443ba0b84b08310a4))
+* Distinguish optimistic managed auth reauth ([41059ae](https://github.com/kernel/kernel-python-sdk/commit/41059ae6d345b924d7e508e43a8b1d0842c58556))
+* Implement Search API v1 providers ([76b3d88](https://github.com/kernel/kernel-python-sdk/commit/76b3d8871e29d47cfda20f68ab23152daf1790c6))
+* Persist stable managed auth completion timestamps ([17a7960](https://github.com/kernel/kernel-python-sdk/commit/17a7960d9304fdceb54d59b09277651b6166fdb1))
+* Publish restricted_route_unavailable and unknown proxy_error codes ([64805ae](https://github.com/kernel/kernel-python-sdk/commit/64805ae68f0978264fb77801e8e94a470a039e94))
+* Support native prepared Adyen Sessions checkout ([5f2df0a](https://github.com/kernel/kernel-python-sdk/commit/5f2df0a4170aa146eabb23f576195d1a06621925))
+
+## [0.110.0](https://github.com/kernel/kernel-python-sdk/compare/v0.109.0...v0.110.0) (2026-09-18)
+
+
+### Features
+
+* Add credential field labels to the API ([63de01f](https://github.com/kernel/kernel-python-sdk/commit/63de01fee886a6758fa613a279d6c401d32a0aff))
+
+## [0.109.0](https://github.com/kernel/kernel-python-sdk/compare/v0.108.0...v0.109.0) (2026-09-17)
+
+
+### Features
+
+* Accept opaque AgentCard vaulted card IDs ([569a604](https://github.com/kernel/kernel-python-sdk/commit/569a604ad58c278c83bda5068c8a6080334d085f))
+* Expose organization-wide concurrent browser capacity ([8b22180](https://github.com/kernel/kernel-python-sdk/commit/8b22180766c592d1480e26fc781ffd5e1b623051))
+* Preserve credential field order ([1a408c4](https://github.com/kernel/kernel-python-sdk/commit/1a408c41c0fd043ff69187f7d62bc721f9e5f867))
+
+## [0.108.0](https://github.com/kernel/kernel-python-sdk/compare/v0.107.0...v0.108.0) (2026-09-17)
+
+
+### Features
+
+* feat: add config registry analysis waiter ([e38c887](https://github.com/kernel/kernel-python-sdk/commit/e38c8878f9f80f5d28fce9f92df6f0a18a48e2bb))
+
+## [0.107.0](https://github.com/kernel/kernel-python-sdk/compare/v0.106.0...v0.107.0) (2026-09-16)
+
+
+### Features
+
+* Classify unevaluable config registry targets ([12ced86](https://github.com/kernel/kernel-python-sdk/commit/12ced8647c23aa43c625b288ed48c7cdf8da9125))
+* Expose persistent Browser REPL through the API ([755dba5](https://github.com/kernel/kernel-python-sdk/commit/755dba5d1273ef9083ffbc4bc7f4adf30aea2fe9))
+* Return Link spend request errors directly ([82d9f84](https://github.com/kernel/kernel-python-sdk/commit/82d9f8400a761a37debabeb79e6e5d8bec2cbe32))
+
+## 0.106.0 (2026-09-16)
+
+Full Changelog: [v0.104.0...v0.106.0](https://github.com/kernel/kernel-python-sdk/compare/v0.104.0...v0.106.0)
+
+### Features
+
+* Add processor-bound AgentCard preparation contracts ([37d5e9c](https://github.com/kernel/kernel-python-sdk/commit/37d5e9c3d5db76bece8f8ba8d5358b939bc79315))
+* Add start_url to browser session updates ([82a475d](https://github.com/kernel/kernel-python-sdk/commit/82a475dae250abce535476386be88033a5314656))
+* Polish and publish the Config Registry API ([afe2fea](https://github.com/kernel/kernel-python-sdk/commit/afe2feaf2dcea1f4fc160d66cfe9f3711b61a447))
+* Square UKP and Hypeman placement load factors ([76dee03](https://github.com/kernel/kernel-python-sdk/commit/76dee03d215481de6ee42b2cba631b5f5893c1a3))
+
+## [0.104.0](https://github.com/kernel/kernel-python-sdk/compare/v0.103.0...v0.104.0) (2026-09-15)
+
+
+### Features
+
+* Expose the WebMCP `awaiting_submission` status ([9b5cb34](https://github.com/kernel/kernel-python-sdk/commit/9b5cb34e8fb19c22a44c42aca9e1fc61bf7744a9))
+
+## [0.103.0](https://github.com/kernel/kernel-python-sdk/compare/v0.102.0...v0.103.0) (2026-09-15)
+
+
+### Features
+
+* chore(stlc): seal custom-code tracking files ([4ce4ab9](https://github.com/kernel/kernel-python-sdk/commit/4ce4ab9f533b4f5d9fe420f91f1a0054c0e13c1b))
+* Expose managed auth field input modes ([c110358](https://github.com/kernel/kernel-python-sdk/commit/c11035847c9f6dd23b7cdd5efa0a8b188d5a850a))
+* Return working config registry configurations ([0737fe9](https://github.com/kernel/kernel-python-sdk/commit/0737fe974b15c6e422c8722d1d76c97c811533d7))
+
+
+### Bug Fixes
+
+* disable automatic vault operation retries in SDKs ([aad62f9](https://github.com/kernel/kernel-python-sdk/commit/aad62f97e2f6c3ee590775916364c19980dc9456))
+
+## [0.102.0](https://github.com/kernel/kernel-python-sdk/compare/v0.101.0...v0.102.0) (2026-09-14)
+
+
+### Features
+
+* Accept a workload intent and use it to widen vendor coverage ([39ffff8](https://github.com/kernel/kernel-python-sdk/commit/39ffff82be3987e2220d8d5057d8d2505991a3eb))
+* Add guarded vault card fill operations ([3927c96](https://github.com/kernel/kernel-python-sdk/commit/3927c961789b4427a72c979ee5bb3dbcae1b8c74))
+* Add single-use AgentCard prepare_checkout for Square ([c439359](https://github.com/kernel/kernel-python-sdk/commit/c43935913b8a471421ef8d96c9066c442ec95171))
+* Allow replacing AgentCard cards after unknown creates ([22a8731](https://github.com/kernel/kernel-python-sdk/commit/22a87318e6ac2f1318a295b9d22de5b2306e66b2))
+* Document punctuation key sequences ([e7850fd](https://github.com/kernel/kernel-python-sdk/commit/e7850fd5757934c731373c5cb09b4444a29d7e84))
+* Honor managed auth browser regions ([0fa2fa3](https://github.com/kernel/kernel-python-sdk/commit/0fa2fa34cd72d04a12c6f9afd61d87098d43799b))
+* Reapply vendor guidance recommendations ([dfcc7ec](https://github.com/kernel/kernel-python-sdk/commit/dfcc7ecbaa087a07d11a5b0eb4738b41b8fadd8b))
+* Report proxy-restricted targets from config registry lookup ([c276ebc](https://github.com/kernel/kernel-python-sdk/commit/c276ebc6fd266924894b300b13ff02f30d0c8527))
+
+## [0.101.0](https://github.com/kernel/kernel-python-sdk/compare/v0.100.0...v0.101.0) (2026-09-11)
+
+
+### Features
+
+* Add config analysis lifecycle guarantees ([e0d9d71](https://github.com/kernel/kernel-python-sdk/commit/e0d9d715689585224cde848845ebaed90c404f64))
+* Brand origin TLS timeouts and classify provider failures ([b00a694](https://github.com/kernel/kernel-python-sdk/commit/b00a6942ff9c831f624e87c12923ee109f2d6cf7))
+* Clarify proxy country defaults ([bb00cda](https://github.com/kernel/kernel-python-sdk/commit/bb00cdaabdeb569ed1bf46b538d15de8768b9686))
+* Expose vault access in organization entitlements ([1c3b388](https://github.com/kernel/kernel-python-sdk/commit/1c3b388f1a4597f65ed3926a8ec2646f5e0fc994))
+* Integrate configurable vault providers across API and checkout ([478bc09](https://github.com/kernel/kernel-python-sdk/commit/478bc09cc5c8f6b2850628d02b88d699cc35328f))
+* Limit free organizations to three vaults ([b0d5628](https://github.com/kernel/kernel-python-sdk/commit/b0d5628acbe0a9d9d006a3349ba1338fdc3d4402))
+* Populate safe failure reasons on invocation responses ([7162cbb](https://github.com/kernel/kernel-python-sdk/commit/7162cbbed49eb4d0a8d431bbb84680e48bdce8a2))
+* Return vendor guidance with config registry recommendations ([938e40b](https://github.com/kernel/kernel-python-sdk/commit/938e40b2a9f4e37ef8221787debbb948e019eeeb))
+* Revert vendor guidance recommendations ([adacc13](https://github.com/kernel/kernel-python-sdk/commit/adacc1321a9c1cc1624e5adf6c2521b0f09851f4))
+* Support international ISP proxy countries ([78adb8a](https://github.com/kernel/kernel-python-sdk/commit/78adb8a4ae929028497e30b0a5988050eee7c1f4))
+
+## [0.100.0](https://github.com/kernel/kernel-python-sdk/compare/v0.99.0...v0.100.0) (2026-09-04)
+
+
+### Features
+
+* Fix vault provider errors and remove test card mode ([043d252](https://github.com/kernel/kernel-python-sdk/commit/043d2521509d4e44a4b3e43694db3a77bd6fe546))
+
+## [0.99.0](https://github.com/kernel/kernel-python-sdk/compare/v0.98.0...v0.99.0) (2026-09-04)
+
+
+### Features
+
+* Expose OTLP destination delivery health ([9ab1d58](https://github.com/kernel/kernel-python-sdk/commit/9ab1d583d7bb4dabba5a640883dda9f2ac248f18))
+* Repair Vault SDK custom-code seals ([645ce00](https://github.com/kernel/kernel-python-sdk/commit/645ce00d68172a323d11b7e4dd4f3997cc287fe0))
+
+## [0.98.0](https://github.com/kernel/kernel-python-sdk/compare/v0.97.0...v0.98.0) (2026-09-02)
+
+
+### Features
+
+* Accept browser session name on all /browsers/{id_or_name} sub-resource routes ([a60fa2b](https://github.com/kernel/kernel-python-sdk/commit/a60fa2bd48b06c8ca61005c3e29c42918ee371f2))
+
+## [0.97.0](https://github.com/kernel/kernel-python-sdk/compare/v0.96.0...v0.97.0) (2026-08-31)
+
+
+### Features
+
+* Expose captcha task and challenge outcomes ([42c33c6](https://github.com/kernel/kernel-python-sdk/commit/42c33c6e08ed6018cd93ddf6704ea70b206ca159))
+* Unify managed auth reauth eligibility and blockers ([8e11fb2](https://github.com/kernel/kernel-python-sdk/commit/8e11fb2ab0b6ddddcb28af0d503489b94cfba1f0))
+
+## [0.96.0](https://github.com/kernel/kernel-python-sdk/compare/v0.95.0...v0.96.0) (2026-08-27)
+
+
+### Features
+
+* Rename the site configs API to config registry ([114c986](https://github.com/kernel/kernel-python-sdk/commit/114c986f0d771049ec09741c65e0be317c98f718))
+
+## [0.95.0](https://github.com/kernel/kernel-python-sdk/compare/v0.94.0...v0.95.0) (2026-08-26)
+
+
+### Features
+
+* Add OTLP destination delivery health storage ([516cbce](https://github.com/kernel/kernel-python-sdk/commit/516cbce77afab43b346d9e95df2e4a25757c474b))
+* route process calls directly to browser VMs ([f833c55](https://github.com/kernel/kernel-python-sdk/commit/f833c55fee9e7c33c444af6366890a9c876e149c))
+
+## [0.94.0](https://github.com/kernel/kernel-python-sdk/compare/v0.93.0...v0.94.0) (2026-08-24)
+
+
+### Features
+
+* Mirror the control/platform telemetry split into the public API ([27c5a0a](https://github.com/kernel/kernel-python-sdk/commit/27c5a0a75ade568f1feb7e058070771ae1f6db60))
+* site configs: report proxy-restricted analyses ([7afcfc4](https://github.com/kernel/kernel-python-sdk/commit/7afcfc499d7008407c872d75bcc4d8a1604f7954))
+
+## [0.93.0](https://github.com/kernel/kernel-python-sdk/compare/v0.92.0...v0.93.0) (2026-08-19)
+
+
+### Features
+
+* Add proxy_error to browser telemetry event schema ([5973ab6](https://github.com/kernel/kernel-python-sdk/commit/5973ab6c766ee31929a523e3ea21fb000bc6879a))
+* Bind managed auth submissions to interactions ([63fb060](https://github.com/kernel/kernel-python-sdk/commit/63fb0606910b17a99f7a584e425fbd77e2d562e5))
+* Harden canonical managed auth interactions ([3522fcb](https://github.com/kernel/kernel-python-sdk/commit/3522fcb9e70854ad2900585f5b37222e7f0e1b26))
+
+## [0.92.0](https://github.com/kernel/kernel-python-sdk/compare/v0.91.0...v0.92.0) (2026-08-17)
+
+
+### Features
+
+* chore(stlc): seal custom-code tracking files ([5b74e46](https://github.com/kernel/kernel-python-sdk/commit/5b74e460198b174e69f0bf690b01f216e89b3dd0))
+
+## [0.91.0](https://github.com/kernel/kernel-python-sdk/compare/v0.90.0...v0.91.0) (2026-08-14)
+
+
+### Features
+
+* Add customer-facing OTLP destination CRUD API ([8479adf](https://github.com/kernel/kernel-python-sdk/commit/8479adff62631c996a4a9129fd8639c16d96b92b))
+* Expose configurable browser memory ([ccf4127](https://github.com/kernel/kernel-python-sdk/commit/ccf4127272adc749f0da75b7e2458e4a9e183423))
+* Require a name on credential providers and backfill unnamed rows ([0d5a4c2](https://github.com/kernel/kernel-python-sdk/commit/0d5a4c29be2e2bc0a216436cbda4bc8602784d02))
+
+## [0.90.0](https://github.com/kernel/kernel-python-sdk/compare/v0.89.0...v0.90.0) (2026-08-12)
+
+
+### Features
+
+* Preserve canonical managed auth input metadata ([3bb6956](https://github.com/kernel/kernel-python-sdk/commit/3bb6956e8021a955cfcbd12fd93d2a3a6b648ed7))
+* Support project selection by ID or name ([67970b2](https://github.com/kernel/kernel-python-sdk/commit/67970b2b8cc0312b81d10e6fec6c3a016c79d347))
+
+## [0.89.0](https://github.com/kernel/kernel-python-sdk/compare/v0.88.0...v0.89.0) (2026-08-12)
+
+
+### Features
+
+* Add region as a first-class API field with plan and flag gating ([fdb020d](https://github.com/kernel/kernel-python-sdk/commit/fdb020d437b3b410b2d5758691f2818d5e744580))
+* Add typed network config with private_hosts to browsers and pools ([b30c0e3](https://github.com/kernel/kernel-python-sdk/commit/b30c0e31182f09247bbf32823c256e27725fdbfc))
+* Expose plan-derived auth limits on GET /org/limits ([9ff1b44](https://github.com/kernel/kernel-python-sdk/commit/9ff1b445f8f2bb3f9bb24f613e22c8b940fc1d5a))
+
+## [0.88.0](https://github.com/kernel/kernel-python-sdk/compare/v0.87.0...v0.88.0) (2026-08-10)
+
+### Features
+
+* Add typed browser proxy configuration using `mode`, `id`, or `name`
+* Add nested managed-auth browser configuration and per-login browser overrides
+
+## 0.87.0 (2026-08-08)
+
+Full Changelog: [v0.86.1...v0.87.0](https://github.com/kernel/kernel-python-sdk/compare/v0.86.1...v0.87.0)
+
+### Features
+
+* Add audit logs plan paywall to the dashboard ([18fdd7b](https://github.com/kernel/kernel-python-sdk/commit/18fdd7b7e37c0346805e43e7cf4b41f0e75ff29e))
+* Expose authenticated request context ([9da2570](https://github.com/kernel/kernel-python-sdk/commit/9da25705b9d0c5deca086bc78a41436f2d123837))
+* Expose profile save behavior in browser responses ([b211a4f](https://github.com/kernel/kernel-python-sdk/commit/b211a4f83b6a3e9eb4b9e41e8493057f57be6cbf))
+* Profiles, file I/O, replays: ungate for Free (packaging PR 3) ([6135e12](https://github.com/kernel/kernel-python-sdk/commit/6135e1288bc08b74532c76f65c5f96851ff98793))
+
+## 0.86.1 (2026-08-06)
+
+Full Changelog: [v0.86.0...v0.86.1](https://github.com/kernel/kernel-python-sdk/compare/v0.86.0...v0.86.1)
+
+## 0.86.0 (2026-08-03)
+
+Full Changelog: [v0.85.0...v0.86.0](https://github.com/kernel/kernel-python-sdk/compare/v0.85.0...v0.86.0)
+
+### Features
+
+* Harden managed auth verification and login recovery ([1d83850](https://github.com/kernel/kernel-python-sdk/commit/1d83850b39851ccfe34a229e2489d834609ffeeb))
+* Report the unified concurrency ceiling from the org limits endpoint ([63775d8](https://github.com/kernel/kernel-python-sdk/commit/63775d8640b52f7b26e40343f870370166ab53dc))
+
+## 0.85.0 (2026-07-29)
+
+Full Changelog: [v0.84.0...v0.85.0](https://github.com/kernel/kernel-python-sdk/compare/v0.84.0...v0.85.0)
+
+### Features
+
+* Add encrypted per-proxy CA bundle for BYO MITM proxies ([c3f4571](https://github.com/kernel/kernel-python-sdk/commit/c3f4571357f66bc38352169f603da1256ea5b152))
+* Stabilize project lifecycle error codes ([5102f24](https://github.com/kernel/kernel-python-sdk/commit/5102f24f2583e0bfa8b21539cb822a65451b3df8))
+
+## 0.84.0 (2026-07-27)
+
+Full Changelog: [v0.83.0...v0.84.0](https://github.com/kernel/kernel-python-sdk/compare/v0.83.0...v0.84.0)
+
+### Features
+
+* Expose telemetry state on managed auth timeline events ([9a37e8c](https://github.com/kernel/kernel-python-sdk/commit/9a37e8cbd40cd0fdcca0486543ddf30677d5dc16))
+
+## 0.83.0 (2026-07-24)
+
+Full Changelog: [v0.82.0...v0.83.0](https://github.com/kernel/kernel-python-sdk/compare/v0.82.0...v0.83.0)
+
+### Features
+
+* Add browser telemetry to managed auth connections ([c19bfbe](https://github.com/kernel/kernel-python-sdk/commit/c19bfbed5eed67758e735fb477c8c6c2afff9e7a))
+
+## 0.82.0 (2026-07-23)
+
+Full Changelog: [v0.81.0...v0.82.0](https://github.com/kernel/kernel-python-sdk/compare/v0.81.0...v0.82.0)
+
+### Features
+
+* Count project names by Unicode code point ([cecdf6d](https://github.com/kernel/kernel-python-sdk/commit/cecdf6d4a4ef7919b6eef5d03f76542b6ade0906))
+* Stream profile downloads as tar archives ([3a2eb97](https://github.com/kernel/kernel-python-sdk/commit/3a2eb97686f3ecf02db16d7bccea803476cb730f))
+* Use format-neutral profile download Accept header ([95d1334](https://github.com/kernel/kernel-python-sdk/commit/95d1334660d2ebaed70d2170233e4bdf3b074958))
+
+## 0.81.0 (2026-07-21)
+
+Full Changelog: [v0.80.0...v0.81.0](https://github.com/kernel/kernel-python-sdk/compare/v0.80.0...v0.81.0)
+
+### Features
+
+* add complete audit log downloads ([18aa8c9](https://github.com/kernel/kernel-python-sdk/commit/18aa8c925b3f7699c30a6202685447432df2da6c))
+
+## 0.80.0 (2026-07-19)
+
+Full Changelog: [v0.79.0...v0.80.0](https://github.com/kernel/kernel-python-sdk/compare/v0.79.0...v0.80.0)
+
+### Features
+
+* Add example to ProxyCheckRequest.url so API reference sample shows it ([2a288ca](https://github.com/kernel/kernel-python-sdk/commit/2a288ca546da7760cdc16e566260fd440ae36fb7))
+* Expose browser sessions on auth timeline events ([687b401](https://github.com/kernel/kernel-python-sdk/commit/687b401117d37d6db8e818cd08ee3983671a23f6))
+* **managed-auth:** add TS CUA worker contract (KERNEL-1456) ([de2fc59](https://github.com/kernel/kernel-python-sdk/commit/de2fc590e00152328ac6a4f5165da0fb727644bb))
+* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([79f96e4](https://github.com/kernel/kernel-python-sdk/commit/79f96e4e8d8f0e04e21c37010e1faa765f17afb6))
+
+## 0.79.0 (2026-07-15)
+
+Full Changelog: [v0.78.1...v0.79.0](https://github.com/kernel/kernel-python-sdk/compare/v0.78.1...v0.79.0)
+
+### Features
+
+* Add telemetry support for browser pools with BAA enforcement ([a7f5d9e](https://github.com/kernel/kernel-python-sdk/commit/a7f5d9e4909fed534df873d90ac14481b18b7d20))
+
+## 0.78.1 (2026-07-15)
+
+Full Changelog: [v0.78.0...v0.78.1](https://github.com/kernel/kernel-python-sdk/compare/v0.78.0...v0.78.1)
+
+### Features
+
+* Default dashboard browser pools to 25% fill rate ([6a4848b](https://github.com/kernel/kernel-python-sdk/commit/6a4848bf17e5caafbd1d04639f30e3c9f9904ff9))
+
+## 0.78.0 (2026-07-13)
+
+Full Changelog: [v0.76.0...v0.78.0](https://github.com/kernel/kernel-python-sdk/compare/v0.76.0...v0.78.0)
+
+### Features
+
+* Add exact-match name filter to list endpoints ([6011556](https://github.com/kernel/kernel-python-sdk/commit/601155642a4d082d19c3b3293eea6d6105f74407))
+* Add name-only rename for profiles and proxies ([1d38266](https://github.com/kernel/kernel-python-sdk/commit/1d3826696cb2005ab6c33a7cd1df19b660d66449))
+* Auto-default refresh_on_profile_update when browser pool profile changes ([4771197](https://github.com/kernel/kernel-python-sdk/commit/4771197c55a1562d465f9b75d8a8e2a12f5e6f76))
+* Document name uniqueness and query match semantics ([e27f37d](https://github.com/kernel/kernel-python-sdk/commit/e27f37d43b1d76628c42f90e77a18af513c86280))
+* Expose telemetry exception message in API/SDK ([6575dd2](https://github.com/kernel/kernel-python-sdk/commit/6575dd21919b092a77de88e4ee8d037f601edadc))
+* Make the browser pool OpenAPI contract truthful ([20b3a7e](https://github.com/kernel/kernel-python-sdk/commit/20b3a7efc61b7de4007fdd9d096595bfab9c06c3))
+* Persist and echo deployment source identity ([5db2b01](https://github.com/kernel/kernel-python-sdk/commit/5db2b01c8ad53441cb3cc7056b8b4b7c8cbcc45c))
+* Support multiple audit log method exclusions ([f9decdd](https://github.com/kernel/kernel-python-sdk/commit/f9decdd9d2f617c4483d3a5ddd568f34ac8052f6))
+
+
+### Documentation
+
+* **openapi:** describe unified concurrency limit, deprecate max_pooled_sessions (CUS-275) ([8c85318](https://github.com/kernel/kernel-python-sdk/commit/8c85318da7d5b064b867b1e4d029937eafc08d13))
+
+## 0.76.0 (2026-07-09)
+
+Full Changelog: [v0.75.0...v0.76.0](https://github.com/kernel/kernel-python-sdk/compare/v0.75.0...v0.76.0)
+
+### Features
+
+* Layer telemetry request config onto the default set ([c6d6b65](https://github.com/kernel/kernel-python-sdk/commit/c6d6b651a377d4283af20ab631f2adf25354954f))
+* Return credential value keys and support removing values on update ([1c4af8e](https://github.com/kernel/kernel-python-sdk/commit/1c4af8ea100bd288182b38792ad87abab4136543))
+
+## 0.75.0 (2026-07-08)
+
+Full Changelog: [v0.74.0...v0.75.0](https://github.com/kernel/kernel-python-sdk/compare/v0.74.0...v0.75.0)
+
+### Features
+
+* Auto-flush pools when a managed auth profile re-authenticates ([430c07f](https://github.com/kernel/kernel-python-sdk/commit/430c07fc2bfd5ab80fe25cc4677b34729bf4b4ee))
+* Document env var redaction on deployment and app reads ([51d713b](https://github.com/kernel/kernel-python-sdk/commit/51d713b92b4c0ed0c2420ea2e128568530a81a14))
+* Expose resolved profile_id and extension_ids on browser pool reads ([56876e9](https://github.com/kernel/kernel-python-sdk/commit/56876e9a49fca34ec34b58ea8f5f85130c0c0315))
+* Reject API key self-deletion ([482f920](https://github.com/kernel/kernel-python-sdk/commit/482f9209f956e5615bdbaa6ec9c58081df56587b))
+* Revert "Store and return a sha256 checksum for uploaded extensions (#… ([74dbc54](https://github.com/kernel/kernel-python-sdk/commit/74dbc54d4d8d5b330f74985227fa09f7569457a7))
+* Store and return a sha256 checksum for uploaded extensions ([b266775](https://github.com/kernel/kernel-python-sdk/commit/b266775497fec10224e965c787c146494eecdae8))
+* Store and return a sha256 checksum for uploaded extensions (reland) ([98cfb28](https://github.com/kernel/kernel-python-sdk/commit/98cfb2835be3d321799bbfd0400e1bb59b39b218))
+
+
+### Documentation
+
+* **api:** clarify reuse/discard_all_idle pool config staleness ([e8641eb](https://github.com/kernel/kernel-python-sdk/commit/e8641ebe96926b22f8874a8ff79498a4cbe7e4ed))
+
+## 0.74.0 (2026-07-06)
+
+Full Changelog: [v0.73.0...v0.74.0](https://github.com/kernel/kernel-python-sdk/compare/v0.73.0...v0.74.0)
+
+### Features
+
+* Add order=desc pagination to telemetry event reads ([763baf5](https://github.com/kernel/kernel-python-sdk/commit/763baf59f95e879d8d9f31566a99fb863354009c))
+* Add tablet and mobile viewport presets to pool dashboard ([37971a0](https://github.com/kernel/kernel-python-sdk/commit/37971a00d405787eebee58a3237c7d4963a0a764))
+
+## 0.73.0 (2026-07-01)
+
+Full Changelog: [v0.72.0...v0.73.0](https://github.com/kernel/kernel-python-sdk/compare/v0.72.0...v0.73.0)
+
+### Features
+
+* Add hidden audit-logs export endpoint ([619c672](https://github.com/kernel/kernel-python-sdk/commit/619c6720f986b835393793896bb5ad4b64f8673f))
+
+## 0.72.0 (2026-06-26)
+
+Full Changelog: [v0.71.0...v0.72.0](https://github.com/kernel/kernel-python-sdk/compare/v0.71.0...v0.72.0)
+
+### Bug Fixes
+
+* **api:** browser pool profile omits save_changes (BrowserPoolProfile) ([34423fb](https://github.com/kernel/kernel-python-sdk/commit/34423fb99af7f43b7b9a6a023a4ec85902b1bcb2))
+
+## 0.71.0 (2026-06-26)
+
+Full Changelog: [v0.70.0...v0.71.0](https://github.com/kernel/kernel-python-sdk/compare/v0.70.0...v0.71.0)
+
+### Features
+
+* Add auth connection event timeline endpoint ([871432f](https://github.com/kernel/kernel-python-sdk/commit/871432f827c95b11d132f60b3547c9986e266f6b))
+* Expose audit logs in public SDK ([9c8e9ac](https://github.com/kernel/kernel-python-sdk/commit/9c8e9ace1cdddc6ce727b38fec62beaba6c6fa20))
+
+## 0.70.0 (2026-06-24)
+
+Full Changelog: [v0.69.0...v0.70.0](https://github.com/kernel/kernel-python-sdk/compare/v0.69.0...v0.70.0)
+
+### Features
+
+* Add GET /browsers/{id}/telemetry/events (read from S2) ([57eb393](https://github.com/kernel/kernel-python-sdk/commit/57eb393ea1eb5fb0d2e5211845f338610198a0fd))
+* Align browser-pool timeout/viewport/fill-rate contract with implementation; reject save_changes on update ([4fddd6b](https://github.com/kernel/kernel-python-sdk/commit/4fddd6bb3f2ccb86ec3cc492311d5fe9c061ba49))
+* api: support per-acquire start_url override on browser pool acquire ([3e11311](https://github.com/kernel/kernel-python-sdk/commit/3e11311f835d558c4fa6ab0c0fb8a9c7e3536a13))
+* **api:** add GET /extensions/{id_or_name}/metadata ([681a8fc](https://github.com/kernel/kernel-python-sdk/commit/681a8fcd7d8ec5c3373a66c81fb405a6844308eb))
+* **api:** resolve GET /org/projects/{id} by ID or name ([52eb598](https://github.com/kernel/kernel-python-sdk/commit/52eb598c7bfd6e696f0d510585439ba5658dc161))
+* Forward replay param through telemetry stream passthrough ([a8001d2](https://github.com/kernel/kernel-python-sdk/commit/a8001d239ae583fa177164e1ce900e767797c853))
+
+
+### Bug Fixes
+
+* don't misroute telemetry/events to the browser VM ([9bcc418](https://github.com/kernel/kernel-python-sdk/commit/9bcc418b4294295c0d8377adfc921c3748d42019))
+
+## 0.69.0 (2026-06-18)
+
+Full Changelog: [v0.68.0...v0.69.0](https://github.com/kernel/kernel-python-sdk/compare/v0.68.0...v0.69.0)
+
+### Features
+
+* Add free-text search to remaining paginated list endpoints ([b34f843](https://github.com/kernel/kernel-python-sdk/commit/b34f843f664307beeb23ec96a1162ee70fd3df1e))
+
+## 0.68.0 (2026-06-15)
+
+Full Changelog: [v0.67.0...v0.68.0](https://github.com/kernel/kernel-python-sdk/compare/v0.67.0...v0.68.0)
+
+### Features
+
+* Add API key rotate endpoint ([e383f7d](https://github.com/kernel/kernel-python-sdk/commit/e383f7d4b6af4f2921200d4a0f6922e730f58e08))
+* **api:** surface deleted/expired API keys for audit trail (KERNEL-1350) ([55e32db](https://github.com/kernel/kernel-python-sdk/commit/55e32dbaf07275c63623a31ec6df88c6aa128f8f))
+
+
+### Bug Fixes
+
+* **pagination:** correct type:ignore placement; stop on 0 sentinel ([5d2afd7](https://github.com/kernel/kernel-python-sdk/commit/5d2afd7df0f2d21d754ca936286ac3a92762e9db))
+* **pagination:** fail loudly on contradictory headers; test both classes ([4554697](https://github.com/kernel/kernel-python-sdk/commit/4554697a9d850848b602a2422199fb2ce72d8efc))
+* **pagination:** stop skipping a page per auto-pagination iteration ([c0a3e14](https://github.com/kernel/kernel-python-sdk/commit/c0a3e14b51e39dfdba37bacf023b2f98d4a3be69))
+
+
+### Styles
+
+* ruff import-sort and format the pagination test ([843fade](https://github.com/kernel/kernel-python-sdk/commit/843fadeb07fb7cebd5d2689eb78511bb8ee0d293))
+
+
+### Refactors
+
+* **api:** align API key audit surface with browser sibling (KERNEL-1350) ([12868d1](https://github.com/kernel/kernel-python-sdk/commit/12868d18dbb081c0e998299b799ac693dd878d0f))
+
+## 0.67.0 (2026-06-11)
+
+Full Changelog: [v0.66.0...v0.67.0](https://github.com/kernel/kernel-python-sdk/compare/v0.66.0...v0.67.0)
+
+### Features
+
+* Add project_id SDK client option mapped to X-Kernel-Project-Id ([1799732](https://github.com/kernel/kernel-python-sdk/commit/17997322232374c6c49d99333b564a0537f247c0))
+
+
+### Documentation
+
+* **api:** correct project-scoping descriptions in OpenAPI spec ([5929332](https://github.com/kernel/kernel-python-sdk/commit/592933283ff7bf0e6596ddbfbc4851e285a1d36e))
+
+## 0.66.0 (2026-06-10)
+
+Full Changelog: [v0.65.0...v0.66.0](https://github.com/kernel/kernel-python-sdk/compare/v0.65.0...v0.66.0)
+
+### Features
+
+* Add org-level default per-project concurrency cap ([2de4525](https://github.com/kernel/kernel-python-sdk/commit/2de45255a1f5ed003ec0492461ee967bf961db2e))
+* Support updating browser session name and tags via PATCH ([e26e9c6](https://github.com/kernel/kernel-python-sdk/commit/e26e9c6b0513a1c7f9723554f4ba977988290d5e))
+
+## 0.65.0 (2026-06-08)
+
+Full Changelog: [v0.64.0...v0.65.0](https://github.com/kernel/kernel-python-sdk/compare/v0.64.0...v0.65.0)
+
+### Features
+
+* **api:** allow setting a name and tags on a pool-acquired browser session ([2977911](https://github.com/kernel/kernel-python-sdk/commit/2977911151def0f4193c5e8063ab33800d167585))
+* **api:** support id-or-name lookup on browser session get/patch/delete ([7e6d287](https://github.com/kernel/kernel-python-sdk/commit/7e6d28795b4b2b9f9e14cf14711a72053530b576))
+
+## 0.64.0 (2026-06-05)
+
+Full Changelog: [v0.63.0...v0.64.0](https://github.com/kernel/kernel-python-sdk/compare/v0.63.0...v0.64.0)
+
+### Features
+
+* Telemetry: expose opt-in categories + full event taxonomy (public API) ([a1502dd](https://github.com/kernel/kernel-python-sdk/commit/a1502dd2639b97fb14ec57bfef5e37668b379db7))
+
+## 0.63.0 (2026-06-05)
+
+Full Changelog: [v0.62.0...v0.63.0](https://github.com/kernel/kernel-python-sdk/compare/v0.62.0...v0.63.0)
+
+### Features
+
+* **api:** allow setting a custom name on a browser session at create time ([0dea358](https://github.com/kernel/kernel-python-sdk/commit/0dea35847a9559c86038774966bae28911c0a044))
+* **api:** allow setting key-value tags on a browser session at create time ([f9ca7c1](https://github.com/kernel/kernel-python-sdk/commit/f9ca7c19aa71326c97b14d723dfd79840e3e8dd7))
+
+
+### Documentation
+
+* **api:** use neutral example for browser session name field ([01b15f6](https://github.com/kernel/kernel-python-sdk/commit/01b15f602a4c21ab4e757bf46ed2d4c1481c14ce))
+
+## 0.62.0 (2026-06-04)
+
+Full Changelog: [v0.61.0...v0.62.0](https://github.com/kernel/kernel-python-sdk/compare/v0.61.0...v0.62.0)
+
+### Features
+
+* api: paginate GET /browser_pools ([c25d693](https://github.com/kernel/kernel-python-sdk/commit/c25d693e310a1035d74654018f68b311f37545d0))
+* api: paginate GET /extensions ([55c3324](https://github.com/kernel/kernel-python-sdk/commit/55c332493f9c7efd21ef8a2fdae40c8318e704cb))
+* api: paginate GET /org/credential_providers ([01897bc](https://github.com/kernel/kernel-python-sdk/commit/01897bc9f7215580aa2f2ca41bfcf1b4d29e9005))
+* api: paginate GET /proxies ([7ff0bdf](https://github.com/kernel/kernel-python-sdk/commit/7ff0bdf181029f0711b694fb6498fbc75183b0f3))
+
+## 0.61.0 (2026-06-03)
+
+Full Changelog: [v0.60.0...v0.61.0](https://github.com/kernel/kernel-python-sdk/compare/v0.60.0...v0.61.0)
+
+### Features
+
+* Add record_audio option to browser replay recording API ([7d357c2](https://github.com/kernel/kernel-python-sdk/commit/7d357c27c32315ba3a7fcaddac0f6563aab91849))
+
+## 0.60.0 (2026-06-03)
+
+Full Changelog: [v0.59.0...v0.60.0](https://github.com/kernel/kernel-python-sdk/compare/v0.59.0...v0.60.0)
+
+### Features
+
+* Add API-backed API key management endpoints ([55bd31e](https://github.com/kernel/kernel-python-sdk/commit/55bd31edfed74f3bc4b08f83e07448e2e53378b0))
+* **examples:** add browser-telemetry example ([4c29993](https://github.com/kernel/kernel-python-sdk/commit/4c29993cdda50fcde19ee7bf696a268cd8b0eb0b))
+* Fix browser pool update schema ([903fe13](https://github.com/kernel/kernel-python-sdk/commit/903fe13b84242acea9c27bc451c16fb5cd58e40c))
+* route browser telemetry directly to the VM by default ([cb50725](https://github.com/kernel/kernel-python-sdk/commit/cb5072516416627ffde7198900484c46dda5b9dc))
+
+
+### Bug Fixes
+
+* **streaming:** don't dispatch empty SSE keepalive comment frames ([a0ee2b2](https://github.com/kernel/kernel-python-sdk/commit/a0ee2b2653c581839be11bb29be5b68166e80658))
+
+## 0.59.0 (2026-06-03)
+
+Full Changelog: [v0.58.0...v0.59.0](https://github.com/kernel/kernel-python-sdk/compare/v0.58.0...v0.59.0)
+
+### Features
+
+* api: surface category field on browser telemetry events ([06a2f7b](https://github.com/kernel/kernel-python-sdk/commit/06a2f7babbc8f6ea31e46f3dfeb8d09dbaae88f0))
+* **api:** move browser telemetry SSE stream to /browsers/{id}/telemetry/stream ([5de851a](https://github.com/kernel/kernel-python-sdk/commit/5de851a5ff708a3e42e276e3cf9e2fe7ef0f2ed6))
+* Support Byteful mobile proxies ([8c0ce1c](https://github.com/kernel/kernel-python-sdk/commit/8c0ce1c1533a538483419f4da0937a6153eb5273))
+
+
+### Bug Fixes
+
+* **api:** move batch + get_mouse_position into Browser Computer Controls tag ([7cbc848](https://github.com/kernel/kernel-python-sdk/commit/7cbc848fe33394ee05f5eb96d62cdc8b5b9b9d9c))
+
+## 0.58.0 (2026-05-27)
+
+Full Changelog: [v0.57.0...v0.58.0](https://github.com/kernel/kernel-python-sdk/compare/v0.57.0...v0.58.0)
+
+### Features
+
+* [codex] Expose API keys in SDK config ([41b3aa2](https://github.com/kernel/kernel-python-sdk/commit/41b3aa243c39af3614154af0059b0299c4e41664))
+* Fix API key request model SDK metadata ([3ced474](https://github.com/kernel/kernel-python-sdk/commit/3ced474265441099b9fd26f26050ec799e804fac))
+* Support telemetry enabled request config and fix SDK metadata ([a1054a6](https://github.com/kernel/kernel-python-sdk/commit/a1054a6b72e79e0fffc88b1b46661683a8a953b8))
+
+## 0.57.0 (2026-05-26)
+
+Full Changelog: [v0.57.0...v0.57.0](https://github.com/kernel/kernel-python-sdk/compare/v0.57.0...v0.57.0)
+
+### Features
+
+* api: dual-route /projects under /org/projects, deprecate /projects ([c5f21ec](https://github.com/kernel/kernel-python-sdk/commit/c5f21ecf3e1081c774ba58ad32b10e8cd5b0a94a))
+* Support telemetry enabled request config ([f48cbee](https://github.com/kernel/kernel-python-sdk/commit/f48cbee40aee158615972e6f39b92179e06efbce))
+
+## 0.57.0 (2026-05-22)
+
+Full Changelog: [v0.56.0...v0.57.0](https://github.com/kernel/kernel-python-sdk/compare/v0.56.0...v0.57.0)
+
+### Features
+
+* [kernel-1116] browser events api integration ([bfd4043](https://github.com/kernel/kernel-python-sdk/commit/bfd404368adf8a4305aabe2fad448e7eeba40322))
+* **api:** type can_reauth_reason as an enum on ManagedAuth ([c9a6c75](https://github.com/kernel/kernel-python-sdk/commit/c9a6c7571ca9eb18b3ef11974571fa1426d2dd61))
+* browsers: accept chrome_policy on POST /browsers (KERNEL-1216) ([b1807a2](https://github.com/kernel/kernel-python-sdk/commit/b1807a2d690f206e7482f5862acde36e692d07aa))
+* EOL persistent browsers: openapi limits ([253be85](https://github.com/kernel/kernel-python-sdk/commit/253be85c497bf65329c484e28a3732b3bd5b3208))
+
+## 0.56.0 (2026-05-18)
+
+Full Changelog: [v0.55.0...v0.56.0](https://github.com/kernel/kernel-python-sdk/compare/v0.55.0...v0.56.0)
+
+### Features
+
+* Expose POST /projects in public API ([4d70271](https://github.com/kernel/kernel-python-sdk/commit/4d702711a74e050ab121f0ce7b2bbe276904a5de))
+
+## 0.55.0 (2026-05-15)
+
+Full Changelog: [v0.53.0...v0.55.0](https://github.com/kernel/kernel-python-sdk/compare/v0.53.0...v0.55.0)
+
+### Features
+
+* Add health check and auto-reauth controls for managed auth connections ([9a0655e](https://github.com/kernel/kernel-python-sdk/commit/9a0655efbeef25e4929397b04f492644339f839e))
+* Polish start URL OpenAPI descriptions ([4f16fb0](https://github.com/kernel/kernel-python-sdk/commit/4f16fb073e0d295fd962e8a8aabd55bdf3144e00))
+
## 0.53.0 (2026-05-12)
Full Changelog: [v0.52.0...v0.53.0](https://github.com/kernel/kernel-python-sdk/compare/v0.52.0...v0.53.0)
diff --git a/README.md b/README.md
index 2ac7afba..a8c9fc1c 100644
--- a/README.md
+++ b/README.md
@@ -185,8 +185,8 @@ first_page = await client.deployments.list(
)
print(
- f"the current start offset for this page: {first_page.next_offset}"
-) # => "the current start offset for this page: 1"
+ f"the offset where the next page starts: {first_page.next_offset}"
+) # => "the offset where the next page starts: 2"
for deployment in first_page.items:
print(deployment.id)
@@ -203,9 +203,9 @@ from kernel import Kernel
client = Kernel()
browser = client.browsers.create(
- persistence={"id": "my-awesome-browser-for-user-1234"},
+ location={},
)
-print(browser.persistence)
+print(browser.location)
```
## File uploads
diff --git a/api.md b/api.md
index 3dea16a3..5463b586 100644
--- a/api.md
+++ b/api.md
@@ -74,16 +74,72 @@ Methods:
- client.invocations.follow(id, \*\*params) -> InvocationFollowResponse
- client.invocations.list_browsers(id) -> InvocationListBrowsersResponse
+# ConfigRegistry
+
+Types:
+
+```python
+from kernel.types import (
+ Analysis,
+ AnalysisSummary,
+ Browser,
+ ConfigRegistryResponse,
+ Evidence,
+ LookupRequest,
+ LookupResponse,
+ NoRecommendation,
+ Proxy,
+ Recommendation,
+ RecommendationResult,
+ RecommendationSummary,
+ ResolveRequest,
+ Target,
+)
+```
+
+Methods:
+
+- client.config_registry.list(\*\*params) -> SyncOffsetPagination[RecommendationSummary]
+- client.config_registry.lookup(\*\*params) -> LookupResponse
+- client.config_registry.resolve(\*\*params) -> ConfigRegistryResponse
+
+## Analyses
+
+Methods:
+
+- client.config_registry.analyses.retrieve(id) -> ConfigRegistryResponse
+- client.config_registry.analyses.list(\*\*params) -> SyncOffsetPagination[AnalysisSummary]
+- client.config_registry.analyses.cancel(id) -> ConfigRegistryResponse
+
# Browsers
Types:
```python
from kernel.types import (
- BrowserPersistence,
+ BrowserAllowlistState,
+ BrowserLocation,
+ BrowserLocationRequest,
+ BrowserLocationSource,
+ BrowserLocationUpdateRequest,
+ BrowserMemory,
+ BrowserMemoryRequest,
+ BrowserNetworkConfig,
+ BrowserNetworkUpdate,
BrowserPoolRef,
+ BrowserProxy,
+ BrowserProxyConfig,
+ BrowserProxyMode,
+ BrowserReplContent,
+ BrowserReplImageContent,
+ BrowserReplRequest,
+ BrowserReplResult,
+ BrowserReplTextContent,
BrowserUsage,
+ BrowserVideoMemory,
Profile,
+ Tags,
+ VaultReference,
BrowserCreateResponse,
BrowserRetrieveResponse,
BrowserUpdateResponse,
@@ -95,13 +151,79 @@ from kernel.types import (
Methods:
- client.browsers.create(\*\*params) -> BrowserCreateResponse
-- client.browsers.retrieve(id, \*\*params) -> BrowserRetrieveResponse
-- client.browsers.update(id, \*\*params) -> BrowserUpdateResponse
+- client.browsers.retrieve(id_or_name, \*\*params) -> BrowserRetrieveResponse
+- client.browsers.update(id_or_name, \*\*params) -> BrowserUpdateResponse
- client.browsers.list(\*\*params) -> SyncOffsetPagination[BrowserListResponse]
-- client.browsers.delete(\*\*params) -> None
-- client.browsers.curl(id, \*\*params) -> BrowserCurlResponse
-- client.browsers.delete_by_id(id) -> None
-- client.browsers.load_extensions(id, \*\*params) -> None
+- client.browsers.curl(id_or_name, \*\*params) -> BrowserCurlResponse
+- client.browsers.delete_by_id(id_or_name) -> None
+- client.browsers.load_extensions(id_or_name, \*\*params) -> None
+- client.browsers.repl(id_or_name, \*\*params) -> BrowserReplResult
+
+## Telemetry
+
+Types:
+
+```python
+from kernel.types.browsers import (
+ BrowserAPICallEvent,
+ BrowserCallStack,
+ BrowserCaptchaChallengeResultEvent,
+ BrowserCaptchaSolveResultEvent,
+ BrowserCaptchaSolveStartedEvent,
+ BrowserCdpCommandEvent,
+ BrowserCdpCommandMethod,
+ BrowserCdpConnectEvent,
+ BrowserCdpDisconnectEvent,
+ BrowserConsoleErrorEvent,
+ BrowserConsoleLogEvent,
+ BrowserEventContext,
+ BrowserEventSource,
+ BrowserHTTPHeaders,
+ BrowserInteractionClickEvent,
+ BrowserInteractionKeyEvent,
+ BrowserInteractionScrollSettledEvent,
+ BrowserLiveViewConnectEvent,
+ BrowserLiveViewDisconnectEvent,
+ BrowserMonitorDisconnectedEvent,
+ BrowserMonitorInitFailedEvent,
+ BrowserMonitorReconnectFailedEvent,
+ BrowserMonitorReconnectedEvent,
+ BrowserMonitorScreenshotEvent,
+ BrowserNetworkIdleEvent,
+ BrowserNetworkLoadingFailedEvent,
+ BrowserNetworkRequestEvent,
+ BrowserNetworkResponseEvent,
+ BrowserPageCrashedEvent,
+ BrowserPageDomContentLoadedEvent,
+ BrowserPageLayoutSettledEvent,
+ BrowserPageLayoutShiftEvent,
+ BrowserPageLcpEvent,
+ BrowserPageLoadEvent,
+ BrowserPageNavigationEvent,
+ BrowserPageNavigationSettledEvent,
+ BrowserPageTabOpenedEvent,
+ BrowserPlatformAPICallEvent,
+ BrowserProxyErrorEvent,
+ BrowserServiceCrashedEvent,
+ BrowserSystemOomKillEvent,
+ BrowserTelemetryCategoriesConfig,
+ BrowserTelemetryCategoryConfig,
+ BrowserTelemetryCdpControlConfig,
+ BrowserTelemetryConfig,
+ BrowserTelemetryControlConfig,
+ BrowserTelemetryEvent,
+ BrowserTelemetryExportConfig,
+ BrowserTelemetryOtlpExportConfig,
+ BrowserTelemetryStorageConfig,
+ TelemetryEventsResponse,
+ TelemetryStreamResponse,
+)
+```
+
+Methods:
+
+- client.browsers.telemetry.events(id_or_name, \*\*params) -> SyncOffsetPagination[TelemetryEventsResponse]
+- client.browsers.telemetry.stream(id_or_name, \*\*params) -> TelemetryStreamResponse
## Replays
@@ -113,10 +235,10 @@ from kernel.types.browsers import ReplayListResponse, ReplayStartResponse
Methods:
-- client.browsers.replays.list(id) -> ReplayListResponse
-- client.browsers.replays.download(replay_id, \*, id) -> BinaryAPIResponse
-- client.browsers.replays.start(id, \*\*params) -> ReplayStartResponse
-- client.browsers.replays.stop(replay_id, \*, id) -> None
+- client.browsers.replays.list(id_or_name) -> ReplayListResponse
+- client.browsers.replays.download(replay_id, \*, id_or_name) -> BinaryAPIResponse
+- client.browsers.replays.start(id_or_name, \*\*params) -> ReplayStartResponse
+- client.browsers.replays.stop(replay_id, \*, id_or_name) -> None
## Fs
@@ -128,18 +250,18 @@ from kernel.types.browsers import FFileInfoResponse, FListFilesResponse
Methods:
-- client.browsers.fs.create_directory(id, \*\*params) -> None
-- client.browsers.fs.delete_directory(id, \*\*params) -> None
-- client.browsers.fs.delete_file(id, \*\*params) -> None
-- client.browsers.fs.download_dir_zip(id, \*\*params) -> BinaryAPIResponse
-- client.browsers.fs.file_info(id, \*\*params) -> FFileInfoResponse
-- client.browsers.fs.list_files(id, \*\*params) -> FListFilesResponse
-- client.browsers.fs.move(id, \*\*params) -> None
-- client.browsers.fs.read_file(id, \*\*params) -> BinaryAPIResponse
-- client.browsers.fs.set_file_permissions(id, \*\*params) -> None
-- client.browsers.fs.upload(id, \*\*params) -> None
-- client.browsers.fs.upload_zip(id, \*\*params) -> None
-- client.browsers.fs.write_file(id, contents, \*\*params) -> None
+- client.browsers.fs.create_directory(id_or_name, \*\*params) -> None
+- client.browsers.fs.delete_directory(id_or_name, \*\*params) -> None
+- client.browsers.fs.delete_file(id_or_name, \*\*params) -> None
+- client.browsers.fs.download_dir_zip(id_or_name, \*\*params) -> BinaryAPIResponse
+- client.browsers.fs.file_info(id_or_name, \*\*params) -> FFileInfoResponse
+- client.browsers.fs.list_files(id_or_name, \*\*params) -> FListFilesResponse
+- client.browsers.fs.move(id_or_name, \*\*params) -> None
+- client.browsers.fs.read_file(id_or_name, \*\*params) -> BinaryAPIResponse
+- client.browsers.fs.set_file_permissions(id_or_name, \*\*params) -> None
+- client.browsers.fs.upload(id_or_name, \*\*params) -> None
+- client.browsers.fs.upload_zip(id_or_name, \*\*params) -> None
+- client.browsers.fs.write_file(id_or_name, contents, \*\*params) -> None
### Watch
@@ -151,9 +273,9 @@ from kernel.types.browsers.fs import WatchEventsResponse, WatchStartResponse
Methods:
-- client.browsers.fs.watch.events(watch_id, \*, id) -> WatchEventsResponse
-- client.browsers.fs.watch.start(id, \*\*params) -> WatchStartResponse
-- client.browsers.fs.watch.stop(watch_id, \*, id) -> None
+- client.browsers.fs.watch.events(watch_id, \*, id_or_name) -> WatchEventsResponse
+- client.browsers.fs.watch.start(id_or_name, \*\*params) -> WatchStartResponse
+- client.browsers.fs.watch.stop(watch_id, \*, id_or_name) -> None
## Process
@@ -173,19 +295,19 @@ from kernel.types.browsers import (
Methods:
-- client.browsers.process.exec(id, \*\*params) -> ProcessExecResponse
-- client.browsers.process.kill(process_id, \*, id, \*\*params) -> ProcessKillResponse
-- client.browsers.process.resize(process_id, \*, id, \*\*params) -> ProcessResizeResponse
-- client.browsers.process.spawn(id, \*\*params) -> ProcessSpawnResponse
-- client.browsers.process.status(process_id, \*, id) -> ProcessStatusResponse
-- client.browsers.process.stdin(process_id, \*, id, \*\*params) -> ProcessStdinResponse
-- client.browsers.process.stdout_stream(process_id, \*, id) -> ProcessStdoutStreamResponse
+- client.browsers.process.exec(id_or_name, \*\*params) -> ProcessExecResponse
+- client.browsers.process.kill(process_id, \*, id_or_name, \*\*params) -> ProcessKillResponse
+- client.browsers.process.resize(process_id, \*, id_or_name, \*\*params) -> ProcessResizeResponse
+- client.browsers.process.spawn(id_or_name, \*\*params) -> ProcessSpawnResponse
+- client.browsers.process.status(process_id, \*, id_or_name) -> ProcessStatusResponse
+- client.browsers.process.stdin(process_id, \*, id_or_name, \*\*params) -> ProcessStdinResponse
+- client.browsers.process.stdout_stream(process_id, \*, id_or_name) -> ProcessStdoutStreamResponse
## Logs
Methods:
-- client.browsers.logs.stream(id, \*\*params) -> LogEvent
+- client.browsers.logs.stream(id_or_name, \*\*params) -> LogEvent
## Computer
@@ -201,30 +323,81 @@ from kernel.types.browsers import (
Methods:
-- client.browsers.computer.batch(id, \*\*params) -> None
-- client.browsers.computer.capture_screenshot(id, \*\*params) -> BinaryAPIResponse
-- client.browsers.computer.click_mouse(id, \*\*params) -> None
-- client.browsers.computer.drag_mouse(id, \*\*params) -> None
-- client.browsers.computer.get_mouse_position(id) -> ComputerGetMousePositionResponse
-- client.browsers.computer.move_mouse(id, \*\*params) -> None
-- client.browsers.computer.press_key(id, \*\*params) -> None
-- client.browsers.computer.read_clipboard(id) -> ComputerReadClipboardResponse
-- client.browsers.computer.scroll(id, \*\*params) -> None
-- client.browsers.computer.set_cursor_visibility(id, \*\*params) -> ComputerSetCursorVisibilityResponse
-- client.browsers.computer.type_text(id, \*\*params) -> None
-- client.browsers.computer.write_clipboard(id, \*\*params) -> None
+- client.browsers.computer.batch(id_or_name, \*\*params) -> None
+- client.browsers.computer.capture_screenshot(id_or_name, \*\*params) -> BinaryAPIResponse
+- client.browsers.computer.click_mouse(id_or_name, \*\*params) -> None
+- client.browsers.computer.drag_mouse(id_or_name, \*\*params) -> None
+- client.browsers.computer.get_mouse_position(id_or_name) -> ComputerGetMousePositionResponse
+- client.browsers.computer.move_mouse(id_or_name, \*\*params) -> None
+- client.browsers.computer.press_key(id_or_name, \*\*params) -> None
+- client.browsers.computer.read_clipboard(id_or_name) -> ComputerReadClipboardResponse
+- client.browsers.computer.scroll(id_or_name, \*\*params) -> None
+- client.browsers.computer.set_cursor_visibility(id_or_name, \*\*params) -> ComputerSetCursorVisibilityResponse
+- client.browsers.computer.type_text(id_or_name, \*\*params) -> None
+- client.browsers.computer.write_clipboard(id_or_name, \*\*params) -> None
## Playwright
Types:
```python
-from kernel.types.browsers import PlaywrightExecuteResponse
+from kernel.types.browsers import ExecutorLimitError, Tab, PlaywrightExecuteResponse
+```
+
+Methods:
+
+- client.browsers.playwright.execute(id_or_name, \*\*params) -> PlaywrightExecuteResponse
+
+### Executors
+
+Types:
+
+```python
+from kernel.types.browsers.playwright import Executor, ExecutorList
```
Methods:
-- client.browsers.playwright.execute(id, \*\*params) -> PlaywrightExecuteResponse
+- client.browsers.playwright.executors.list(id_or_name) -> ExecutorList
+- client.browsers.playwright.executors.delete(name, \*, id_or_name, \*\*params) -> None
+
+## Webmcp
+
+Types:
+
+```python
+from kernel.types.browsers import (
+ CustomToolSource,
+ InvocationFailure,
+ InvocationResult,
+ InvokeRequest,
+ Tool,
+ ToolAnnotations,
+ ToolFrame,
+ ToolMetadata,
+ ToolSource,
+ ToolsResponse,
+)
+```
+
+Methods:
+
+- client.browsers.webmcp.invoke_tool(id_or_name, \*\*params) -> InvocationResult
+- client.browsers.webmcp.list_tools(id_or_name, \*\*params) -> ToolsResponse
+
+### CustomTools
+
+Types:
+
+```python
+from kernel.types.browsers.webmcp import AddRequest, CustomToolsResponse, Definition, Match
+```
+
+Methods:
+
+- client.browsers.webmcp.custom_tools.list(id_or_name) -> CustomToolsResponse
+- client.browsers.webmcp.custom_tools.add(id_or_name, \*\*params) -> CustomToolsResponse
+- client.browsers.webmcp.custom_tools.remove(id, \*, id_or_name) -> None
# Profiles
@@ -232,12 +405,25 @@ Methods:
- client.profiles.create(\*\*params) -> Profile
- client.profiles.retrieve(id_or_name) -> Profile
+- client.profiles.update(id_or_name, \*\*params) -> Profile
- client.profiles.list(\*\*params) -> SyncOffsetPagination[Profile]
- client.profiles.delete(id_or_name) -> None
-- client.profiles.download(id_or_name) -> BinaryAPIResponse
+- client.profiles.download(id_or_name, \*\*params) -> BinaryAPIResponse
# Auth
+## Context
+
+Types:
+
+```python
+from kernel.types.auth import AuthContext
+```
+
+Methods:
+
+- client.auth.context.retrieve() -> AuthContext
+
## Connections
Types:
@@ -246,7 +432,9 @@ Types:
from kernel.types.auth import (
LoginResponse,
ManagedAuth,
+ ManagedAuthBrowserConfig,
ManagedAuthCreateRequest,
+ ManagedAuthTimelineEvent,
ManagedAuthUpdateRequest,
SubmitFieldsRequest,
SubmitFieldsResponse,
@@ -264,6 +452,25 @@ Methods:
- client.auth.connections.follow(id) -> ConnectionFollowResponse
- client.auth.connections.login(id, \*\*params) -> LoginResponse
- client.auth.connections.submit(id, \*\*params) -> SubmitFieldsResponse
+- client.auth.connections.timeline(id, \*\*params) -> SyncOffsetPagination[ManagedAuthTimelineEvent]
+
+# Telemetry
+
+## Destinations
+
+Types:
+
+```python
+from kernel.types.telemetry import OtlpDestination
+```
+
+Methods:
+
+- client.telemetry.destinations.create(\*\*params) -> OtlpDestination
+- client.telemetry.destinations.retrieve(id_or_name) -> OtlpDestination
+- client.telemetry.destinations.update(id_or_name, \*\*params) -> OtlpDestination
+- client.telemetry.destinations.list(\*\*params) -> SyncOffsetPagination[OtlpDestination]
+- client.telemetry.destinations.delete(id_or_name) -> None
# Proxies
@@ -273,6 +480,7 @@ Types:
from kernel.types import (
ProxyCreateResponse,
ProxyRetrieveResponse,
+ ProxyUpdateResponse,
ProxyListResponse,
ProxyCheckResponse,
)
@@ -281,25 +489,27 @@ from kernel.types import (
Methods:
- client.proxies.create(\*\*params) -> ProxyCreateResponse
-- client.proxies.retrieve(id) -> ProxyRetrieveResponse
-- client.proxies.list() -> ProxyListResponse
-- client.proxies.delete(id) -> None
-- client.proxies.check(id, \*\*params) -> ProxyCheckResponse
+- 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_or_name) -> None
+- client.proxies.check(id_or_name, \*\*params) -> ProxyCheckResponse
# Extensions
Types:
```python
-from kernel.types import ExtensionListResponse, ExtensionUploadResponse
+from kernel.types import ExtensionListResponse, ExtensionGetResponse, ExtensionUploadResponse
```
Methods:
-- client.extensions.list() -> ExtensionListResponse
+- client.extensions.list(\*\*params) -> SyncOffsetPagination[ExtensionListResponse]
- client.extensions.delete(id_or_name) -> None
- client.extensions.download(id_or_name) -> BinaryAPIResponse
- client.extensions.download_from_chrome_store(\*\*params) -> BinaryAPIResponse
+- client.extensions.get(id_or_name) -> ExtensionGetResponse
- client.extensions.upload(\*\*params) -> ExtensionUploadResponse
# BrowserPools
@@ -307,7 +517,7 @@ Methods:
Types:
```python
-from kernel.types import BrowserPool, BrowserPoolListResponse, BrowserPoolAcquireResponse
+from kernel.types import BrowserPool, BrowserPoolAcquireResponse
```
Methods:
@@ -315,12 +525,129 @@ Methods:
- client.browser_pools.create(\*\*params) -> BrowserPool
- client.browser_pools.retrieve(id_or_name) -> BrowserPool
- client.browser_pools.update(id_or_name, \*\*params) -> BrowserPool
-- client.browser_pools.list() -> BrowserPoolListResponse
+- client.browser_pools.list(\*\*params) -> SyncOffsetPagination[BrowserPool]
- client.browser_pools.delete(id_or_name, \*\*params) -> None
- client.browser_pools.acquire(id_or_name, \*\*params) -> BrowserPoolAcquireResponse
- client.browser_pools.flush(id_or_name) -> None
- client.browser_pools.release(id_or_name, \*\*params) -> None
+# VaultProviderConfigs
+
+Types:
+
+```python
+from kernel.types import VaultProviderConfig
+```
+
+Methods:
+
+- client.vault_provider_configs.create(\*\*params) -> VaultProviderConfig
+- client.vault_provider_configs.retrieve(id_or_name) -> VaultProviderConfig
+- client.vault_provider_configs.update(id_or_name, \*\*params) -> VaultProviderConfig
+- client.vault_provider_configs.list(\*\*params) -> SyncOffsetPagination[VaultProviderConfig]
+- client.vault_provider_configs.delete(id_or_name) -> None
+
+# Vaults
+
+Types:
+
+```python
+from kernel.types import Vault, VaultEncryptionKey
+```
+
+Methods:
+
+- client.vaults.retrieve(id_or_name) -> Vault
+- client.vaults.list(\*\*params) -> SyncOffsetPagination[Vault]
+- client.vaults.delete(id_or_name) -> None
+- client.vaults.retrieve_encryption_key(id_or_name) -> VaultEncryptionKey
+- client.vaults.upsert(\*\*params) -> Vault
+
+## Items
+
+Types:
+
+```python
+from kernel.types.vaults import (
+ AgentcardCheckoutAuthorization,
+ AgentcardCheckoutPreparation,
+ AgentcardPreparedProcessor,
+ AuthorizeVaultItemOperationRequest,
+ CardVaultItemSpec,
+ CardVaultItemState,
+ CollectVaultItemOperationRequest,
+ ConfirmTransactionVaultItemOperationRequest,
+ CredentialAccountVaultItem,
+ CredentialAccountVaultItemRequest,
+ CredentialCollectionAction,
+ CredentialVaultFieldDefinition,
+ CredentialVaultFieldDefinitionInput,
+ CredentialVaultFieldInput,
+ CredentialVaultFieldState,
+ CredentialVaultFieldType,
+ CredentialVaultFieldUpdate,
+ CredentialVaultItem,
+ CredentialVaultItemRequest,
+ CredentialVaultItemSpec,
+ CredentialVaultItemSpecInput,
+ CredentialVaultItemSpecUpdate,
+ CredentialVaultItemState,
+ CredentialVaultItemUpdateRequest,
+ FillVaultItemOperationRequest,
+ FillVaultItemOperationResult,
+ KernelCardState,
+ KernelCardVaultItemSpec,
+ KernelCredentialVaultItemSpec,
+ KernelCredentialVaultItemSpecInput,
+ KernelCredentialVaultItemState,
+ KernelWalletState,
+ KernelWalletVaultItemSpec,
+ ManagedAuthCredentialVaultField,
+ ManagedAuthCredentialVaultItemSpec,
+ ManagedAuthCredentialVaultItemSpecInput,
+ ManagedAuthCredentialVaultItemState,
+ OnePasswordCredentialAccountSpec,
+ OnePasswordCredentialAccountState,
+ OnePasswordCredentialVaultItemSpec,
+ OnePasswordCredentialVaultItemSpecInput,
+ OnePasswordCredentialVaultItemState,
+ OnePasswordFillVaultItemOperationRequest,
+ OnePasswordFillVaultItemOperationResult,
+ OnePasswordOAuthAction,
+ OnePasswordRecoverVaultItemOperationRequest,
+ OnePasswordRequestAccessVaultItemOperationRequest,
+ PrepareCheckoutVaultItemOperationRequest,
+ RestartVerificationVaultItemOperationRequest,
+ VaultCardAliases,
+ VaultCardFillField,
+ VaultCheckoutContext,
+ VaultFillField,
+ VaultFillFieldResult,
+ VaultItem,
+ VaultItemAction,
+ VaultItemEvent,
+ VaultItemOperationResponse,
+ VaultPaymentMethod,
+ VaultWebmcpBinding,
+ WalletVaultItemSpec,
+ WalletVaultItemState,
+ WebmcpInvokeVaultItemOperationRequest,
+ WebmcpInvokeVaultItemOperationResult,
+ ItemListResponse,
+ ItemEventsResponse,
+)
+```
+
+Methods:
+
+- client.vaults.items.retrieve(key, \*, id_or_name, \*\*params) -> VaultItem
+- client.vaults.items.update(key, \*, id_or_name, \*\*params) -> VaultItem
+- client.vaults.items.list(id_or_name) -> ItemListResponse
+- client.vaults.items.delete(key, \*, id_or_name) -> None
+- client.vaults.items.events(key, \*, id_or_name, \*\*params) -> ItemEventsResponse
+- client.vaults.items.perform_operation(key, \*, id_or_name, \*\*params) -> VaultItemOperationResponse
+- client.vaults.items.upsert(key, \*, id_or_name, \*\*params) -> VaultItem
+
# Credentials
Types:
@@ -353,11 +680,11 @@ 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
+- client.projects.create(\*\*params) -> Project
+- client.projects.retrieve(id_or_name) -> Project
+- client.projects.update(id_or_name, \*\*params) -> Project
+- client.projects.list(\*\*params) -> SyncOffsetPagination[Project]
+- client.projects.delete(id_or_name) -> None
## Limits
@@ -369,8 +696,87 @@ from kernel.types.projects import ProjectLimits, UpdateProjectLimitsRequest
Methods:
-- client.projects.limits.retrieve(id) -> ProjectLimits
-- client.projects.limits.update(id, \*\*params) -> ProjectLimits
+- client.projects.limits.retrieve(id_or_name) -> ProjectLimits
+- client.projects.limits.update(id_or_name, \*\*params) -> ProjectLimits
+
+# Organization
+
+## Entitlements
+
+Types:
+
+```python
+from kernel.types.organization import OrgEntitlements
+```
+
+Methods:
+
+- client.organization.entitlements.retrieve() -> OrgEntitlements
+
+## Limits
+
+Types:
+
+```python
+from kernel.types.organization import OrgLimits, UpdateOrgLimitsRequest
+```
+
+Methods:
+
+- client.organization.limits.retrieve() -> OrgLimits
+- client.organization.limits.update(\*\*params) -> OrgLimits
+
+# AuditLogs
+
+Types:
+
+```python
+from kernel.types import AuditLogEntry
+```
+
+Methods:
+
+- client.audit_logs.list(\*\*params) -> SyncPageTokenPagination[AuditLogEntry]
+- client.audit_logs.export_chunk(\*\*params) -> BinaryAPIResponse
+
+## ExportDestinations
+
+Types:
+
+```python
+from kernel.types.audit_logs import (
+ AuditLogExportDestination,
+ AuditLogExportDestinationTestResult,
+ CreateAuditLogExportDestinationRequest,
+ UpdateAuditLogExportDestinationRequest,
+)
+```
+
+Methods:
+
+- client.audit_logs.export_destinations.create(\*\*params) -> AuditLogExportDestination
+- client.audit_logs.export_destinations.retrieve(id) -> AuditLogExportDestination
+- client.audit_logs.export_destinations.update(id, \*\*params) -> AuditLogExportDestination
+- client.audit_logs.export_destinations.list(\*\*params) -> SyncOffsetPagination[AuditLogExportDestination]
+- client.audit_logs.export_destinations.delete(id) -> None
+- client.audit_logs.export_destinations.test(id) -> AuditLogExportDestinationTestResult
+
+# APIKeys
+
+Types:
+
+```python
+from kernel.types import APIKey, CreatedAPIKey
+```
+
+Methods:
+
+- client.api_keys.create(\*\*params) -> CreatedAPIKey
+- client.api_keys.retrieve(id, \*\*params) -> APIKey
+- client.api_keys.update(id, \*\*params) -> APIKey
+- client.api_keys.list(\*\*params) -> SyncOffsetPagination[APIKey]
+- client.api_keys.delete(id) -> None
+- client.api_keys.rotate(id, \*\*params) -> CreatedAPIKey
# CredentialProviders
@@ -383,7 +789,6 @@ from kernel.types import (
CredentialProviderItem,
CredentialProviderTestResult,
UpdateCredentialProviderRequest,
- CredentialProviderListResponse,
CredentialProviderListItemsResponse,
)
```
@@ -393,7 +798,44 @@ Methods:
- client.credential_providers.create(\*\*params) -> CredentialProvider
- client.credential_providers.retrieve(id) -> CredentialProvider
- client.credential_providers.update(id, \*\*params) -> CredentialProvider
-- client.credential_providers.list() -> CredentialProviderListResponse
+- client.credential_providers.list(\*\*params) -> SyncOffsetPagination[CredentialProvider]
- client.credential_providers.delete(id) -> None
- client.credential_providers.list_items(id) -> CredentialProviderListItemsResponse
- client.credential_providers.test(id) -> CredentialProviderTestResult
+
+# Search
+
+Types:
+
+```python
+from kernel.types import Attempt, ProviderTarget, Request, Result, Search, Strategy, Usage, Warning
+```
+
+Methods:
+
+- client.search.create(\*\*params) -> Search
+- client.search.retrieve(id) -> Search
+
+## Contents
+
+Types:
+
+```python
+from kernel.types.search import FetchRequest, Response
+```
+
+Methods:
+
+- client.search.contents.fetch(id, \*\*params) -> Response
+
+## Providers
+
+Types:
+
+```python
+from kernel.types.search import Provider, ProviderListResponse
+```
+
+Methods:
+
+- client.search.providers.list(\*\*params) -> ProviderListResponse
diff --git a/examples/browser_telemetry.py b/examples/browser_telemetry.py
new file mode 100644
index 00000000..8d1795c4
--- /dev/null
+++ b/examples/browser_telemetry.py
@@ -0,0 +1,32 @@
+"""Example: stream live browser telemetry events from a session."""
+
+from kernel import Kernel
+
+
+def main() -> None:
+ client = Kernel()
+
+ # Enable telemetry capture when creating the browser.
+ browser = client.browsers.create(telemetry={"enabled": True})
+
+ try:
+ # Telemetry is a default direct-to-VM routing subresource, so the stream
+ # connects straight to the browser VM automatically.
+ stream = client.browsers.telemetry.stream(browser.session_id)
+
+ # Make a few browser activity calls to generate events. The "api" telemetry
+ # category emits an event per VM API call, so events arrive within ~1s.
+ for _ in range(3):
+ client.browsers.curl(browser.session_id, url="https://example.com", method="GET")
+
+ # Print a few events, then stop so we don't wait on the 15s keepalive.
+ for count, message in enumerate(stream, start=1):
+ print(message.seq, message.event.type)
+ if count >= 3:
+ break
+ finally:
+ client.browsers.delete_by_id(browser.session_id)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyproject.toml b/pyproject.toml
index 10375b3b..eca42025 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "kernel"
-version = "0.53.0"
+version = "0.128.0"
description = "The official Python library for the kernel API"
dynamic = ["readme"]
license = "Apache-2.0"
@@ -12,7 +12,8 @@ dependencies = [
"httpx>=0.23.0, <1",
"pydantic>=1.9.0, <3",
"typing-extensions>=4.14, <5",
- "anyio>=3.5.0, <5",
+ "anyio>=4.14.2, <5; python_version >= '3.10'",
+ "anyio>=3.5.0, <5; python_version < '3.10'",
"distro>=1.7.0, <2",
"sniffio",
]
diff --git a/release-please-config.json b/release-please-config.json
index 942ec08a..6ffb6a57 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -2,14 +2,15 @@
"packages": {
".": {}
},
- "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json",
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"include-v-in-tag": true,
"include-component-in-tag": false,
"versioning": "prerelease",
- "prerelease": true,
+ "prerelease": false,
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": false,
"pull-request-header": "Automated Release PR",
+ "pull-request-footer": "Merge this pull request with a merge commit. Merging creates the GitHub release and publishes the package.",
"pull-request-title-pattern": "release: ${version}",
"changelog-sections": [
{
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 4a38b203..dd39217d 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -19,7 +19,7 @@ aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
-anyio==4.12.1
+anyio==4.14.2
# via httpx
# via kernel
argcomplete==3.6.3
@@ -48,7 +48,7 @@ exceptiongroup==1.3.1
# via pytest
execnet==2.1.2
# via pytest-xdist
-filelock==3.19.1
+filelock==3.32.7
# via virtualenv
frozenlist==1.8.0
# via aiohttp
diff --git a/requirements.lock b/requirements.lock
index 5f6c7ff4..ed2c30f2 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -19,7 +19,7 @@ aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
-anyio==4.12.1
+anyio==4.14.2
# via httpx
# via kernel
async-timeout==5.0.1
diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
index 14b2cc82..960fa64b 100755
--- a/scripts/utils/upload-artifact.sh
+++ b/scripts/utils/upload-artifact.sh
@@ -20,7 +20,7 @@ UPLOAD_RESPONSE=$(curl -v -X PUT \
if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
- echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/kernel-python/$SHA/$FILENAME'\033[0m"
+ echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/kernel-python-sdk-staging/$SHA/$FILENAME'\033[0m"
else
echo -e "\033[31mFailed to upload artifact.\033[0m"
exit 1
diff --git a/src/kernel/__init__.py b/src/kernel/__init__.py
index 333d7018..af9462fb 100644
--- a/src/kernel/__init__.py
+++ b/src/kernel/__init__.py
@@ -51,6 +51,11 @@
app_registry,
export_registry,
)
+from .lib.audit_log_download import (
+ AuditLogDownloadError,
+ AuditLogDownloadResult,
+ AuditLogDownloadProgress,
+)
__all__ = [
"types",
@@ -105,6 +110,9 @@
"App",
"app_registry",
"export_registry",
+ "AuditLogDownloadError",
+ "AuditLogDownloadProgress",
+ "AuditLogDownloadResult",
]
if not _t.TYPE_CHECKING:
diff --git a/src/kernel/_base_client.py b/src/kernel/_base_client.py
index 2599dc41..a73c619b 100644
--- a/src/kernel/_base_client.py
+++ b/src/kernel/_base_client.py
@@ -772,6 +772,9 @@ def _calculate_retry_timeout(
timeout = sleep_seconds * jitter
return timeout if timeout >= 0 else 0
+ def _should_retry_on_connection_error(self, _request: httpx.Request) -> bool:
+ return True
+
def _should_retry(self, response: httpx.Response) -> bool:
# Note: this is not a standard header
should_retry_header = response.headers.get("x-should-retry")
@@ -1012,7 +1015,7 @@ def request(
except httpx.TimeoutException as err:
log.debug("Encountered httpx.TimeoutException", exc_info=True)
- if remaining_retries > 0:
+ if remaining_retries > 0 and self._should_retry_on_connection_error(request):
self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
@@ -1026,7 +1029,7 @@ def request(
except Exception as err:
log.debug("Encountered Exception", exc_info=True)
- if remaining_retries > 0:
+ if remaining_retries > 0 and self._should_retry_on_connection_error(request):
self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
@@ -1596,7 +1599,7 @@ async def request(
except httpx.TimeoutException as err:
log.debug("Encountered httpx.TimeoutException", exc_info=True)
- if remaining_retries > 0:
+ if remaining_retries > 0 and self._should_retry_on_connection_error(request):
await self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
@@ -1610,7 +1613,7 @@ async def request(
except Exception as err:
log.debug("Encountered Exception", exc_info=True)
- if remaining_retries > 0:
+ if remaining_retries > 0 and self._should_retry_on_connection_error(request):
await self._sleep_for_retry(
retries_taken=retries_taken,
max_retries=max_retries,
diff --git a/src/kernel/_client.py b/src/kernel/_client.py
index 6c3e3aae..4ffa9faf 100644
--- a/src/kernel/_client.py
+++ b/src/kernel/_client.py
@@ -38,10 +38,18 @@
from .lib.browser_routing.routing import (
BrowserRouteCache,
BrowserRoutingConfig,
- strip_direct_vm_auth,
+ prepare_direct_vm_request,
rewrite_direct_vm_options,
browser_routing_config_from_env,
+ install_direct_vm_auth_stripping,
+ is_stale_direct_vm_auth_response,
+ should_retry_stale_direct_vm_auth,
+ install_stale_direct_vm_auth_eviction,
+ install_async_direct_vm_auth_stripping,
maybe_evict_browser_route_from_response,
+ should_retry_direct_vm_connection_error,
+ install_async_stale_direct_vm_auth_eviction,
+ direct_vm_request_body_is_known_unreplayable,
maybe_populate_browser_route_cache_from_response,
)
@@ -49,19 +57,28 @@
from .resources import (
apps,
auth,
+ search,
+ vaults,
proxies,
+ api_keys,
browsers,
profiles,
projects,
+ telemetry,
+ audit_logs,
extensions,
credentials,
deployments,
invocations,
+ organization,
browser_pools,
+ config_registry,
credential_providers,
+ vault_provider_configs,
)
from .resources.apps import AppsResource, AsyncAppsResource
from .resources.proxies import ProxiesResource, AsyncProxiesResource
+ from .resources.api_keys import APIKeysResource, AsyncAPIKeysResource
from .resources.profiles import ProfilesResource, AsyncProfilesResource
from .resources.auth.auth import AuthResource, AsyncAuthResource
from .resources.extensions import ExtensionsResource, AsyncExtensionsResource
@@ -69,9 +86,16 @@
from .resources.deployments import DeploymentsResource, AsyncDeploymentsResource
from .resources.invocations import InvocationsResource, AsyncInvocationsResource
from .resources.browser_pools import BrowserPoolsResource, AsyncBrowserPoolsResource
+ from .resources.search.search import SearchResource, AsyncSearchResource
+ from .resources.vaults.vaults import VaultsResource, AsyncVaultsResource
from .resources.browsers.browsers import BrowsersResource, AsyncBrowsersResource
from .resources.projects.projects import ProjectsResource, AsyncProjectsResource
+ from .resources.telemetry.telemetry import TelemetryResource, AsyncTelemetryResource
from .resources.credential_providers import CredentialProvidersResource, AsyncCredentialProvidersResource
+ from .resources.audit_logs.audit_logs import AuditLogsResource, AsyncAuditLogsResource
+ from .resources.vault_provider_configs import VaultProviderConfigsResource, AsyncVaultProviderConfigsResource
+ from .resources.organization.organization import OrganizationResource, AsyncOrganizationResource
+ from .resources.config_registry.config_registry import ConfigRegistryResource, AsyncConfigRegistryResource
__all__ = [
"ENVIRONMENTS",
@@ -96,6 +120,9 @@ class Kernel(SyncAPIClient):
api_key: str
browser_route_cache: BrowserRouteCache
+ project_id: str | None
+ project: str | None
+
_environment: Literal["production", "development"] | NotGiven
_browser_routing: BrowserRoutingConfig
@@ -103,6 +130,8 @@ def __init__(
self,
*,
api_key: str | None = None,
+ project_id: str | None = None,
+ project: str | None = None,
environment: Literal["production", "development"] | NotGiven = not_given,
base_url: str | httpx.URL | None | NotGiven = not_given,
timeout: float | Timeout | None | NotGiven = not_given,
@@ -136,6 +165,10 @@ def __init__(
)
self.api_key = api_key
+ self.project_id = project_id
+
+ self.project = project
+
self._environment = environment
base_url_env = os.environ.get("KERNEL_BASE_URL")
@@ -183,6 +216,8 @@ def __init__(
)
self.browser_route_cache = _browser_route_cache or BrowserRouteCache()
self._browser_routing = browser_routing_config_from_env()
+ install_direct_vm_auth_stripping(self._client)
+ install_stale_direct_vm_auth_eviction(self._client)
@cached_property
def deployments(self) -> DeploymentsResource:
@@ -205,9 +240,15 @@ def invocations(self) -> InvocationsResource:
return InvocationsResource(self)
+ @cached_property
+ def config_registry(self) -> ConfigRegistryResource:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ from .resources.config_registry import ConfigRegistryResource
+
+ return ConfigRegistryResource(self)
+
@cached_property
def browsers(self) -> BrowsersResource:
- """Create and manage browser sessions."""
from .resources.browsers import BrowsersResource
return BrowsersResource(self)
@@ -225,6 +266,12 @@ def auth(self) -> AuthResource:
return AuthResource(self)
+ @cached_property
+ def telemetry(self) -> TelemetryResource:
+ from .resources.telemetry import TelemetryResource
+
+ return TelemetryResource(self)
+
@cached_property
def proxies(self) -> ProxiesResource:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -246,6 +293,18 @@ def browser_pools(self) -> BrowserPoolsResource:
return BrowserPoolsResource(self)
+ @cached_property
+ def vault_provider_configs(self) -> VaultProviderConfigsResource:
+ from .resources.vault_provider_configs import VaultProviderConfigsResource
+
+ return VaultProviderConfigsResource(self)
+
+ @cached_property
+ def vaults(self) -> VaultsResource:
+ from .resources.vaults import VaultsResource
+
+ return VaultsResource(self)
+
@cached_property
def credentials(self) -> CredentialsResource:
"""Create and manage credentials for authentication."""
@@ -255,11 +314,35 @@ def credentials(self) -> CredentialsResource:
@cached_property
def projects(self) -> ProjectsResource:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
from .resources.projects import ProjectsResource
return ProjectsResource(self)
+ @cached_property
+ def organization(self) -> OrganizationResource:
+ from .resources.organization import OrganizationResource
+
+ return OrganizationResource(self)
+
+ @cached_property
+ def audit_logs(self) -> AuditLogsResource:
+ """Read audit log records for the authenticated organization."""
+ from .resources.audit_logs import AuditLogsResource
+
+ return AuditLogsResource(self)
+
+ @cached_property
+ def api_keys(self) -> APIKeysResource:
+ """Create and manage API keys for organization and project-scoped access."""
+ from .resources.api_keys import APIKeysResource
+
+ return APIKeysResource(self)
+
@cached_property
def credential_providers(self) -> CredentialProvidersResource:
"""Configure external credential providers like 1Password."""
@@ -267,6 +350,13 @@ def credential_providers(self) -> CredentialProvidersResource:
return CredentialProvidersResource(self)
+ @cached_property
+ def search(self) -> SearchResource:
+ """Search the web and retrieve content for selected results."""
+ from .resources.search import SearchResource
+
+ return SearchResource(self)
+
@cached_property
def with_raw_response(self) -> KernelWithRawResponse:
return KernelWithRawResponse(self)
@@ -292,6 +382,8 @@ def default_headers(self) -> dict[str, str | Omit]:
return {
**super().default_headers,
"X-Stainless-Async": "false",
+ "X-Kernel-Project-Id": self.project_id if self.project_id is not None else Omit(),
+ "X-Kernel-Project": self.project if self.project is not None else Omit(),
**self._custom_headers,
}
@@ -302,7 +394,22 @@ def _prepare_options(self, options: Any) -> Any:
@override
def _prepare_request(self, request: httpx.Request) -> None:
- strip_direct_vm_auth(request, cache=self.browser_route_cache)
+ prepare_direct_vm_request(request, cache=self.browser_route_cache)
+
+ @override
+ def _should_retry_on_connection_error(self, request: httpx.Request) -> bool:
+ return should_retry_direct_vm_connection_error(request)
+
+ @override
+ def _should_retry(self, response: httpx.Response) -> bool:
+ if direct_vm_request_body_is_known_unreplayable(response.request):
+ return False
+ if is_stale_direct_vm_auth_response(response):
+ # The route was already evicted by the response hook; retry only when
+ # the body can be rebuilt, otherwise the caller sees the original auth
+ # failure and a later call goes to the control plane.
+ return should_retry_stale_direct_vm_auth(response)
+ return super()._should_retry(response)
@override
def _process_response(
@@ -330,6 +437,8 @@ def copy(
self,
*,
api_key: str | None = None,
+ project_id: str | None = None,
+ project: str | None = None,
environment: Literal["production", "development"] | None = None,
base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = not_given,
@@ -366,6 +475,8 @@ def copy(
http_client = http_client or self._client
return self.__class__(
api_key=api_key or self.api_key,
+ project_id=project_id or self.project_id,
+ project=project or self.project,
base_url=base_url or self.base_url,
environment=environment or self._environment,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
@@ -420,6 +531,9 @@ class AsyncKernel(AsyncAPIClient):
api_key: str
browser_route_cache: BrowserRouteCache
+ project_id: str | None
+ project: str | None
+
_environment: Literal["production", "development"] | NotGiven
_browser_routing: BrowserRoutingConfig
@@ -427,6 +541,8 @@ def __init__(
self,
*,
api_key: str | None = None,
+ project_id: str | None = None,
+ project: str | None = None,
environment: Literal["production", "development"] | NotGiven = not_given,
base_url: str | httpx.URL | None | NotGiven = not_given,
timeout: float | Timeout | None | NotGiven = not_given,
@@ -460,6 +576,10 @@ def __init__(
)
self.api_key = api_key
+ self.project_id = project_id
+
+ self.project = project
+
self._environment = environment
base_url_env = os.environ.get("KERNEL_BASE_URL")
@@ -507,6 +627,8 @@ def __init__(
)
self.browser_route_cache = _browser_route_cache or BrowserRouteCache()
self._browser_routing = browser_routing_config_from_env()
+ install_async_direct_vm_auth_stripping(self._client)
+ install_async_stale_direct_vm_auth_eviction(self._client)
@cached_property
def deployments(self) -> AsyncDeploymentsResource:
@@ -529,9 +651,15 @@ def invocations(self) -> AsyncInvocationsResource:
return AsyncInvocationsResource(self)
+ @cached_property
+ def config_registry(self) -> AsyncConfigRegistryResource:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ from .resources.config_registry import AsyncConfigRegistryResource
+
+ return AsyncConfigRegistryResource(self)
+
@cached_property
def browsers(self) -> AsyncBrowsersResource:
- """Create and manage browser sessions."""
from .resources.browsers import AsyncBrowsersResource
return AsyncBrowsersResource(self)
@@ -549,6 +677,12 @@ def auth(self) -> AsyncAuthResource:
return AsyncAuthResource(self)
+ @cached_property
+ def telemetry(self) -> AsyncTelemetryResource:
+ from .resources.telemetry import AsyncTelemetryResource
+
+ return AsyncTelemetryResource(self)
+
@cached_property
def proxies(self) -> AsyncProxiesResource:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -570,6 +704,18 @@ def browser_pools(self) -> AsyncBrowserPoolsResource:
return AsyncBrowserPoolsResource(self)
+ @cached_property
+ def vault_provider_configs(self) -> AsyncVaultProviderConfigsResource:
+ from .resources.vault_provider_configs import AsyncVaultProviderConfigsResource
+
+ return AsyncVaultProviderConfigsResource(self)
+
+ @cached_property
+ def vaults(self) -> AsyncVaultsResource:
+ from .resources.vaults import AsyncVaultsResource
+
+ return AsyncVaultsResource(self)
+
@cached_property
def credentials(self) -> AsyncCredentialsResource:
"""Create and manage credentials for authentication."""
@@ -579,11 +725,35 @@ def credentials(self) -> AsyncCredentialsResource:
@cached_property
def projects(self) -> AsyncProjectsResource:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
from .resources.projects import AsyncProjectsResource
return AsyncProjectsResource(self)
+ @cached_property
+ def organization(self) -> AsyncOrganizationResource:
+ from .resources.organization import AsyncOrganizationResource
+
+ return AsyncOrganizationResource(self)
+
+ @cached_property
+ def audit_logs(self) -> AsyncAuditLogsResource:
+ """Read audit log records for the authenticated organization."""
+ from .resources.audit_logs import AsyncAuditLogsResource
+
+ return AsyncAuditLogsResource(self)
+
+ @cached_property
+ def api_keys(self) -> AsyncAPIKeysResource:
+ """Create and manage API keys for organization and project-scoped access."""
+ from .resources.api_keys import AsyncAPIKeysResource
+
+ return AsyncAPIKeysResource(self)
+
@cached_property
def credential_providers(self) -> AsyncCredentialProvidersResource:
"""Configure external credential providers like 1Password."""
@@ -591,6 +761,13 @@ def credential_providers(self) -> AsyncCredentialProvidersResource:
return AsyncCredentialProvidersResource(self)
+ @cached_property
+ def search(self) -> AsyncSearchResource:
+ """Search the web and retrieve content for selected results."""
+ from .resources.search import AsyncSearchResource
+
+ return AsyncSearchResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncKernelWithRawResponse:
return AsyncKernelWithRawResponse(self)
@@ -616,6 +793,8 @@ def default_headers(self) -> dict[str, str | Omit]:
return {
**super().default_headers,
"X-Stainless-Async": f"async:{get_async_library()}",
+ "X-Kernel-Project-Id": self.project_id if self.project_id is not None else Omit(),
+ "X-Kernel-Project": self.project if self.project is not None else Omit(),
**self._custom_headers,
}
@@ -626,7 +805,22 @@ async def _prepare_options(self, options: Any) -> Any:
@override
async def _prepare_request(self, request: httpx.Request) -> None:
- strip_direct_vm_auth(request, cache=self.browser_route_cache)
+ prepare_direct_vm_request(request, cache=self.browser_route_cache)
+
+ @override
+ def _should_retry_on_connection_error(self, request: httpx.Request) -> bool:
+ return should_retry_direct_vm_connection_error(request)
+
+ @override
+ def _should_retry(self, response: httpx.Response) -> bool:
+ if direct_vm_request_body_is_known_unreplayable(response.request):
+ return False
+ if is_stale_direct_vm_auth_response(response):
+ # The route was already evicted by the response hook; retry only when
+ # the body can be rebuilt, otherwise the caller sees the original auth
+ # failure and a later call goes to the control plane.
+ return should_retry_stale_direct_vm_auth(response)
+ return super()._should_retry(response)
@override
async def _process_response(
@@ -654,6 +848,8 @@ def copy(
self,
*,
api_key: str | None = None,
+ project_id: str | None = None,
+ project: str | None = None,
environment: Literal["production", "development"] | None = None,
base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = not_given,
@@ -690,6 +886,8 @@ def copy(
http_client = http_client or self._client
return self.__class__(
api_key=api_key or self.api_key,
+ project_id=project_id or self.project_id,
+ project=project or self.project,
base_url=base_url or self.base_url,
environment=environment or self._environment,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
@@ -766,9 +964,15 @@ def invocations(self) -> invocations.InvocationsResourceWithRawResponse:
return InvocationsResourceWithRawResponse(self._client.invocations)
+ @cached_property
+ def config_registry(self) -> config_registry.ConfigRegistryResourceWithRawResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ from .resources.config_registry import ConfigRegistryResourceWithRawResponse
+
+ return ConfigRegistryResourceWithRawResponse(self._client.config_registry)
+
@cached_property
def browsers(self) -> browsers.BrowsersResourceWithRawResponse:
- """Create and manage browser sessions."""
from .resources.browsers import BrowsersResourceWithRawResponse
return BrowsersResourceWithRawResponse(self._client.browsers)
@@ -786,6 +990,12 @@ def auth(self) -> auth.AuthResourceWithRawResponse:
return AuthResourceWithRawResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.TelemetryResourceWithRawResponse:
+ from .resources.telemetry import TelemetryResourceWithRawResponse
+
+ return TelemetryResourceWithRawResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.ProxiesResourceWithRawResponse:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -807,6 +1017,18 @@ def browser_pools(self) -> browser_pools.BrowserPoolsResourceWithRawResponse:
return BrowserPoolsResourceWithRawResponse(self._client.browser_pools)
+ @cached_property
+ def vault_provider_configs(self) -> vault_provider_configs.VaultProviderConfigsResourceWithRawResponse:
+ from .resources.vault_provider_configs import VaultProviderConfigsResourceWithRawResponse
+
+ return VaultProviderConfigsResourceWithRawResponse(self._client.vault_provider_configs)
+
+ @cached_property
+ def vaults(self) -> vaults.VaultsResourceWithRawResponse:
+ from .resources.vaults import VaultsResourceWithRawResponse
+
+ return VaultsResourceWithRawResponse(self._client.vaults)
+
@cached_property
def credentials(self) -> credentials.CredentialsResourceWithRawResponse:
"""Create and manage credentials for authentication."""
@@ -816,11 +1038,35 @@ def credentials(self) -> credentials.CredentialsResourceWithRawResponse:
@cached_property
def projects(self) -> projects.ProjectsResourceWithRawResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
from .resources.projects import ProjectsResourceWithRawResponse
return ProjectsResourceWithRawResponse(self._client.projects)
+ @cached_property
+ def organization(self) -> organization.OrganizationResourceWithRawResponse:
+ from .resources.organization import OrganizationResourceWithRawResponse
+
+ return OrganizationResourceWithRawResponse(self._client.organization)
+
+ @cached_property
+ def audit_logs(self) -> audit_logs.AuditLogsResourceWithRawResponse:
+ """Read audit log records for the authenticated organization."""
+ from .resources.audit_logs import AuditLogsResourceWithRawResponse
+
+ return AuditLogsResourceWithRawResponse(self._client.audit_logs)
+
+ @cached_property
+ def api_keys(self) -> api_keys.APIKeysResourceWithRawResponse:
+ """Create and manage API keys for organization and project-scoped access."""
+ from .resources.api_keys import APIKeysResourceWithRawResponse
+
+ return APIKeysResourceWithRawResponse(self._client.api_keys)
+
@cached_property
def credential_providers(self) -> credential_providers.CredentialProvidersResourceWithRawResponse:
"""Configure external credential providers like 1Password."""
@@ -828,6 +1074,13 @@ def credential_providers(self) -> credential_providers.CredentialProvidersResour
return CredentialProvidersResourceWithRawResponse(self._client.credential_providers)
+ @cached_property
+ def search(self) -> search.SearchResourceWithRawResponse:
+ """Search the web and retrieve content for selected results."""
+ from .resources.search import SearchResourceWithRawResponse
+
+ return SearchResourceWithRawResponse(self._client.search)
+
class AsyncKernelWithRawResponse:
_client: AsyncKernel
@@ -856,9 +1109,15 @@ def invocations(self) -> invocations.AsyncInvocationsResourceWithRawResponse:
return AsyncInvocationsResourceWithRawResponse(self._client.invocations)
+ @cached_property
+ def config_registry(self) -> config_registry.AsyncConfigRegistryResourceWithRawResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ from .resources.config_registry import AsyncConfigRegistryResourceWithRawResponse
+
+ return AsyncConfigRegistryResourceWithRawResponse(self._client.config_registry)
+
@cached_property
def browsers(self) -> browsers.AsyncBrowsersResourceWithRawResponse:
- """Create and manage browser sessions."""
from .resources.browsers import AsyncBrowsersResourceWithRawResponse
return AsyncBrowsersResourceWithRawResponse(self._client.browsers)
@@ -876,6 +1135,12 @@ def auth(self) -> auth.AsyncAuthResourceWithRawResponse:
return AsyncAuthResourceWithRawResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.AsyncTelemetryResourceWithRawResponse:
+ from .resources.telemetry import AsyncTelemetryResourceWithRawResponse
+
+ return AsyncTelemetryResourceWithRawResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.AsyncProxiesResourceWithRawResponse:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -897,6 +1162,18 @@ def browser_pools(self) -> browser_pools.AsyncBrowserPoolsResourceWithRawRespons
return AsyncBrowserPoolsResourceWithRawResponse(self._client.browser_pools)
+ @cached_property
+ def vault_provider_configs(self) -> vault_provider_configs.AsyncVaultProviderConfigsResourceWithRawResponse:
+ from .resources.vault_provider_configs import AsyncVaultProviderConfigsResourceWithRawResponse
+
+ return AsyncVaultProviderConfigsResourceWithRawResponse(self._client.vault_provider_configs)
+
+ @cached_property
+ def vaults(self) -> vaults.AsyncVaultsResourceWithRawResponse:
+ from .resources.vaults import AsyncVaultsResourceWithRawResponse
+
+ return AsyncVaultsResourceWithRawResponse(self._client.vaults)
+
@cached_property
def credentials(self) -> credentials.AsyncCredentialsResourceWithRawResponse:
"""Create and manage credentials for authentication."""
@@ -906,11 +1183,35 @@ def credentials(self) -> credentials.AsyncCredentialsResourceWithRawResponse:
@cached_property
def projects(self) -> projects.AsyncProjectsResourceWithRawResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
from .resources.projects import AsyncProjectsResourceWithRawResponse
return AsyncProjectsResourceWithRawResponse(self._client.projects)
+ @cached_property
+ def organization(self) -> organization.AsyncOrganizationResourceWithRawResponse:
+ from .resources.organization import AsyncOrganizationResourceWithRawResponse
+
+ return AsyncOrganizationResourceWithRawResponse(self._client.organization)
+
+ @cached_property
+ def audit_logs(self) -> audit_logs.AsyncAuditLogsResourceWithRawResponse:
+ """Read audit log records for the authenticated organization."""
+ from .resources.audit_logs import AsyncAuditLogsResourceWithRawResponse
+
+ return AsyncAuditLogsResourceWithRawResponse(self._client.audit_logs)
+
+ @cached_property
+ def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithRawResponse:
+ """Create and manage API keys for organization and project-scoped access."""
+ from .resources.api_keys import AsyncAPIKeysResourceWithRawResponse
+
+ return AsyncAPIKeysResourceWithRawResponse(self._client.api_keys)
+
@cached_property
def credential_providers(self) -> credential_providers.AsyncCredentialProvidersResourceWithRawResponse:
"""Configure external credential providers like 1Password."""
@@ -918,6 +1219,13 @@ def credential_providers(self) -> credential_providers.AsyncCredentialProvidersR
return AsyncCredentialProvidersResourceWithRawResponse(self._client.credential_providers)
+ @cached_property
+ def search(self) -> search.AsyncSearchResourceWithRawResponse:
+ """Search the web and retrieve content for selected results."""
+ from .resources.search import AsyncSearchResourceWithRawResponse
+
+ return AsyncSearchResourceWithRawResponse(self._client.search)
+
class KernelWithStreamedResponse:
_client: Kernel
@@ -946,9 +1254,15 @@ def invocations(self) -> invocations.InvocationsResourceWithStreamingResponse:
return InvocationsResourceWithStreamingResponse(self._client.invocations)
+ @cached_property
+ def config_registry(self) -> config_registry.ConfigRegistryResourceWithStreamingResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ from .resources.config_registry import ConfigRegistryResourceWithStreamingResponse
+
+ return ConfigRegistryResourceWithStreamingResponse(self._client.config_registry)
+
@cached_property
def browsers(self) -> browsers.BrowsersResourceWithStreamingResponse:
- """Create and manage browser sessions."""
from .resources.browsers import BrowsersResourceWithStreamingResponse
return BrowsersResourceWithStreamingResponse(self._client.browsers)
@@ -966,6 +1280,12 @@ def auth(self) -> auth.AuthResourceWithStreamingResponse:
return AuthResourceWithStreamingResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.TelemetryResourceWithStreamingResponse:
+ from .resources.telemetry import TelemetryResourceWithStreamingResponse
+
+ return TelemetryResourceWithStreamingResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.ProxiesResourceWithStreamingResponse:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -987,6 +1307,18 @@ def browser_pools(self) -> browser_pools.BrowserPoolsResourceWithStreamingRespon
return BrowserPoolsResourceWithStreamingResponse(self._client.browser_pools)
+ @cached_property
+ def vault_provider_configs(self) -> vault_provider_configs.VaultProviderConfigsResourceWithStreamingResponse:
+ from .resources.vault_provider_configs import VaultProviderConfigsResourceWithStreamingResponse
+
+ return VaultProviderConfigsResourceWithStreamingResponse(self._client.vault_provider_configs)
+
+ @cached_property
+ def vaults(self) -> vaults.VaultsResourceWithStreamingResponse:
+ from .resources.vaults import VaultsResourceWithStreamingResponse
+
+ return VaultsResourceWithStreamingResponse(self._client.vaults)
+
@cached_property
def credentials(self) -> credentials.CredentialsResourceWithStreamingResponse:
"""Create and manage credentials for authentication."""
@@ -996,11 +1328,35 @@ def credentials(self) -> credentials.CredentialsResourceWithStreamingResponse:
@cached_property
def projects(self) -> projects.ProjectsResourceWithStreamingResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
from .resources.projects import ProjectsResourceWithStreamingResponse
return ProjectsResourceWithStreamingResponse(self._client.projects)
+ @cached_property
+ def organization(self) -> organization.OrganizationResourceWithStreamingResponse:
+ from .resources.organization import OrganizationResourceWithStreamingResponse
+
+ return OrganizationResourceWithStreamingResponse(self._client.organization)
+
+ @cached_property
+ def audit_logs(self) -> audit_logs.AuditLogsResourceWithStreamingResponse:
+ """Read audit log records for the authenticated organization."""
+ from .resources.audit_logs import AuditLogsResourceWithStreamingResponse
+
+ return AuditLogsResourceWithStreamingResponse(self._client.audit_logs)
+
+ @cached_property
+ def api_keys(self) -> api_keys.APIKeysResourceWithStreamingResponse:
+ """Create and manage API keys for organization and project-scoped access."""
+ from .resources.api_keys import APIKeysResourceWithStreamingResponse
+
+ return APIKeysResourceWithStreamingResponse(self._client.api_keys)
+
@cached_property
def credential_providers(self) -> credential_providers.CredentialProvidersResourceWithStreamingResponse:
"""Configure external credential providers like 1Password."""
@@ -1008,6 +1364,13 @@ def credential_providers(self) -> credential_providers.CredentialProvidersResour
return CredentialProvidersResourceWithStreamingResponse(self._client.credential_providers)
+ @cached_property
+ def search(self) -> search.SearchResourceWithStreamingResponse:
+ """Search the web and retrieve content for selected results."""
+ from .resources.search import SearchResourceWithStreamingResponse
+
+ return SearchResourceWithStreamingResponse(self._client.search)
+
class AsyncKernelWithStreamedResponse:
_client: AsyncKernel
@@ -1036,9 +1399,15 @@ def invocations(self) -> invocations.AsyncInvocationsResourceWithStreamingRespon
return AsyncInvocationsResourceWithStreamingResponse(self._client.invocations)
+ @cached_property
+ def config_registry(self) -> config_registry.AsyncConfigRegistryResourceWithStreamingResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ from .resources.config_registry import AsyncConfigRegistryResourceWithStreamingResponse
+
+ return AsyncConfigRegistryResourceWithStreamingResponse(self._client.config_registry)
+
@cached_property
def browsers(self) -> browsers.AsyncBrowsersResourceWithStreamingResponse:
- """Create and manage browser sessions."""
from .resources.browsers import AsyncBrowsersResourceWithStreamingResponse
return AsyncBrowsersResourceWithStreamingResponse(self._client.browsers)
@@ -1056,6 +1425,12 @@ def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse:
return AsyncAuthResourceWithStreamingResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.AsyncTelemetryResourceWithStreamingResponse:
+ from .resources.telemetry import AsyncTelemetryResourceWithStreamingResponse
+
+ return AsyncTelemetryResourceWithStreamingResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.AsyncProxiesResourceWithStreamingResponse:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -1077,6 +1452,18 @@ def browser_pools(self) -> browser_pools.AsyncBrowserPoolsResourceWithStreamingR
return AsyncBrowserPoolsResourceWithStreamingResponse(self._client.browser_pools)
+ @cached_property
+ def vault_provider_configs(self) -> vault_provider_configs.AsyncVaultProviderConfigsResourceWithStreamingResponse:
+ from .resources.vault_provider_configs import AsyncVaultProviderConfigsResourceWithStreamingResponse
+
+ return AsyncVaultProviderConfigsResourceWithStreamingResponse(self._client.vault_provider_configs)
+
+ @cached_property
+ def vaults(self) -> vaults.AsyncVaultsResourceWithStreamingResponse:
+ from .resources.vaults import AsyncVaultsResourceWithStreamingResponse
+
+ return AsyncVaultsResourceWithStreamingResponse(self._client.vaults)
+
@cached_property
def credentials(self) -> credentials.AsyncCredentialsResourceWithStreamingResponse:
"""Create and manage credentials for authentication."""
@@ -1086,11 +1473,35 @@ def credentials(self) -> credentials.AsyncCredentialsResourceWithStreamingRespon
@cached_property
def projects(self) -> projects.AsyncProjectsResourceWithStreamingResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
from .resources.projects import AsyncProjectsResourceWithStreamingResponse
return AsyncProjectsResourceWithStreamingResponse(self._client.projects)
+ @cached_property
+ def organization(self) -> organization.AsyncOrganizationResourceWithStreamingResponse:
+ from .resources.organization import AsyncOrganizationResourceWithStreamingResponse
+
+ return AsyncOrganizationResourceWithStreamingResponse(self._client.organization)
+
+ @cached_property
+ def audit_logs(self) -> audit_logs.AsyncAuditLogsResourceWithStreamingResponse:
+ """Read audit log records for the authenticated organization."""
+ from .resources.audit_logs import AsyncAuditLogsResourceWithStreamingResponse
+
+ return AsyncAuditLogsResourceWithStreamingResponse(self._client.audit_logs)
+
+ @cached_property
+ def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithStreamingResponse:
+ """Create and manage API keys for organization and project-scoped access."""
+ from .resources.api_keys import AsyncAPIKeysResourceWithStreamingResponse
+
+ return AsyncAPIKeysResourceWithStreamingResponse(self._client.api_keys)
+
@cached_property
def credential_providers(self) -> credential_providers.AsyncCredentialProvidersResourceWithStreamingResponse:
"""Configure external credential providers like 1Password."""
@@ -1098,6 +1509,13 @@ def credential_providers(self) -> credential_providers.AsyncCredentialProvidersR
return AsyncCredentialProvidersResourceWithStreamingResponse(self._client.credential_providers)
+ @cached_property
+ def search(self) -> search.AsyncSearchResourceWithStreamingResponse:
+ """Search the web and retrieve content for selected results."""
+ from .resources.search import AsyncSearchResourceWithStreamingResponse
+
+ return AsyncSearchResourceWithStreamingResponse(self._client.search)
+
Client = Kernel
diff --git a/src/kernel/_response.py b/src/kernel/_response.py
index a6c5fa30..95d81625 100644
--- a/src/kernel/_response.py
+++ b/src/kernel/_response.py
@@ -24,7 +24,7 @@
import httpx
import pydantic
-from ._types import NoneType
+from ._types import NoneType, NoContentAsNone
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
from ._models import BaseModel, is_basemodel
from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
@@ -185,6 +185,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
return cast(R, None)
response = self.http_response
+ if (
+ response.status_code == 204
+ and to is None
+ and isinstance(origin, type)
+ and issubclass(origin, NoContentAsNone)
+ ):
+ return cast(R, None)
if cast_to == str:
return cast(R, response.text)
diff --git a/src/kernel/_streaming.py b/src/kernel/_streaming.py
index 5520edb5..ddca7eb6 100644
--- a/src/kernel/_streaming.py
+++ b/src/kernel/_streaming.py
@@ -251,7 +251,12 @@ def decode(self, line: str) -> ServerSentEvent | None:
# See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501
if not line:
- if not self._event and not self._data and not self._last_event_id and self._retry is None:
+ # Whether to dispatch depends only on what was set in the *current* block. last_event_id
+ # is sticky across events (per the SSE spec, it is intentionally not reset below), so it
+ # must not be part of this check -- otherwise, once any event carries an id, every
+ # subsequent comment-only block (e.g. a ``:\n\n`` keepalive) would dispatch an empty
+ # event, which then fails to JSON-decode in the typed Stream wrapper.
+ if not self._event and not self._data and self._retry is None:
return None
sse = ServerSentEvent(
diff --git a/src/kernel/_types.py b/src/kernel/_types.py
index d924c204..94982715 100644
--- a/src/kernel/_types.py
+++ b/src/kernel/_types.py
@@ -44,6 +44,12 @@
Query = Mapping[str, object]
Body = object
AnyMapping = Mapping[str, object]
+
+
+class NoContentAsNone:
+ pass
+
+
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")
diff --git a/src/kernel/_version.py b/src/kernel/_version.py
index 173fc2c2..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.53.0" # x-release-please-version
+__version__ = "0.128.0" # x-release-please-version
diff --git a/src/kernel/lib/audit_log_download.py b/src/kernel/lib/audit_log_download.py
new file mode 100644
index 00000000..f334d6ae
--- /dev/null
+++ b/src/kernel/lib/audit_log_download.py
@@ -0,0 +1,232 @@
+from __future__ import annotations
+
+import time
+import hashlib
+import inspect
+from typing import BinaryIO, Callable, Optional, Protocol, Awaitable, ContextManager, AsyncContextManager
+from dataclasses import dataclass
+
+import anyio
+import httpx
+from anyio import to_thread
+
+from .._exceptions import KernelError
+
+_DEFAULT_MAX_TRANSFER_RETRIES = 6
+_MAX_CHUNK_ROWS = 50_000
+_MAX_RETRY_DELAY = 8.0
+
+
+class AuditLogDownloadError(KernelError):
+ pass
+
+
+@dataclass(frozen=True)
+class AuditLogDownloadResult:
+ bytes_written: int
+ chunks: int
+ rows: int
+
+
+@dataclass(frozen=True)
+class AuditLogDownloadProgress(AuditLogDownloadResult):
+ chunk_rows: int
+
+
+class _SyncChunkResponse(Protocol):
+ @property
+ def headers(self) -> httpx.Headers: ...
+
+ def read(self) -> bytes: ...
+
+ def close(self) -> None: ...
+
+
+class _AsyncChunkResponse(Protocol):
+ @property
+ def headers(self) -> httpx.Headers: ...
+
+ async def read(self) -> bytes: ...
+
+ async def close(self) -> None: ...
+
+
+SyncFetchChunk = Callable[[Optional[str]], ContextManager[_SyncChunkResponse]]
+AsyncFetchChunk = Callable[[Optional[str]], AsyncContextManager[_AsyncChunkResponse]]
+ProgressCallback = Callable[[AuditLogDownloadProgress], None]
+AsyncProgressCallback = Callable[[AuditLogDownloadProgress], Optional[Awaitable[None]]]
+
+
+def download_audit_logs(
+ fetch_chunk: SyncFetchChunk,
+ destination: BinaryIO,
+ *,
+ on_progress: ProgressCallback | None = None,
+ max_transfer_retries: int = _DEFAULT_MAX_TRANSFER_RETRIES,
+) -> AuditLogDownloadResult:
+ if not callable(getattr(destination, "write", None)):
+ raise TypeError("audit log download destination must provide write()")
+ _validate_max_transfer_retries(max_transfer_retries)
+
+ cursor: str | None = None
+ result = AuditLogDownloadResult(bytes_written=0, chunks=0, rows=0)
+ seen_cursors: set[str] = set()
+ while True:
+ body, headers = _fetch_verified_chunk(fetch_chunk, cursor, max_transfer_retries)
+ chunk_rows, next_cursor, has_more = _parse_chunk_headers(headers, cursor)
+ if has_more and next_cursor is not None:
+ if next_cursor in seen_cursors:
+ raise AuditLogDownloadError("response repeated X-Next-Cursor header")
+ seen_cursors.add(next_cursor)
+ _write_chunk(destination, body)
+
+ cursor = next_cursor
+ result = AuditLogDownloadResult(
+ bytes_written=result.bytes_written + len(body),
+ chunks=result.chunks + 1,
+ rows=result.rows + chunk_rows,
+ )
+ if on_progress is not None:
+ on_progress(
+ AuditLogDownloadProgress(
+ bytes_written=result.bytes_written,
+ chunks=result.chunks,
+ rows=result.rows,
+ chunk_rows=chunk_rows,
+ )
+ )
+ if not has_more:
+ return result
+
+
+async def async_download_audit_logs(
+ fetch_chunk: AsyncFetchChunk,
+ destination: BinaryIO,
+ *,
+ on_progress: AsyncProgressCallback | None = None,
+ max_transfer_retries: int = _DEFAULT_MAX_TRANSFER_RETRIES,
+) -> AuditLogDownloadResult:
+ if not callable(getattr(destination, "write", None)):
+ raise TypeError("audit log download destination must provide write()")
+ _validate_max_transfer_retries(max_transfer_retries)
+
+ cursor: str | None = None
+ result = AuditLogDownloadResult(bytes_written=0, chunks=0, rows=0)
+ seen_cursors: set[str] = set()
+ while True:
+ body, headers = await _async_fetch_verified_chunk(fetch_chunk, cursor, max_transfer_retries)
+ chunk_rows, next_cursor, has_more = _parse_chunk_headers(headers, cursor)
+ if has_more and next_cursor is not None:
+ if next_cursor in seen_cursors:
+ raise AuditLogDownloadError("response repeated X-Next-Cursor header")
+ seen_cursors.add(next_cursor)
+ await to_thread.run_sync(_write_chunk, destination, body)
+
+ cursor = next_cursor
+ result = AuditLogDownloadResult(
+ bytes_written=result.bytes_written + len(body),
+ chunks=result.chunks + 1,
+ rows=result.rows + chunk_rows,
+ )
+ if on_progress is not None:
+ callback_result = on_progress(
+ AuditLogDownloadProgress(
+ bytes_written=result.bytes_written,
+ chunks=result.chunks,
+ rows=result.rows,
+ chunk_rows=chunk_rows,
+ )
+ )
+ if inspect.isawaitable(callback_result):
+ await callback_result
+ if not has_more:
+ return result
+
+
+def _fetch_verified_chunk(
+ fetch_chunk: SyncFetchChunk, cursor: str | None, max_transfer_retries: int
+) -> tuple[bytes, httpx.Headers]:
+ for retries in range(max_transfer_retries + 1):
+ transfer_error: Exception | None = None
+ with fetch_chunk(cursor) as response:
+ try:
+ body = response.read()
+ headers = httpx.Headers(response.headers)
+ _verify_checksum(body, headers)
+ except Exception as error:
+ transfer_error = error
+ else:
+ return body, headers
+ assert transfer_error is not None
+ if retries == max_transfer_retries:
+ raise transfer_error
+ time.sleep(min(2**retries, _MAX_RETRY_DELAY))
+ raise AssertionError("unreachable")
+
+
+async def _async_fetch_verified_chunk(
+ fetch_chunk: AsyncFetchChunk, cursor: str | None, max_transfer_retries: int
+) -> tuple[bytes, httpx.Headers]:
+ for retries in range(max_transfer_retries + 1):
+ transfer_error: Exception | None = None
+ async with fetch_chunk(cursor) as response:
+ try:
+ body = await response.read()
+ headers = httpx.Headers(response.headers)
+ await to_thread.run_sync(_verify_checksum, body, headers)
+ except Exception as error:
+ transfer_error = error
+ else:
+ return body, headers
+ assert transfer_error is not None
+ if retries == max_transfer_retries:
+ raise transfer_error
+ await anyio.sleep(min(2**retries, _MAX_RETRY_DELAY))
+ raise AssertionError("unreachable")
+
+
+def _verify_checksum(body: bytes, headers: httpx.Headers) -> None:
+ expected = headers.get("x-content-sha256")
+ if not expected:
+ raise AuditLogDownloadError("response missing X-Content-Sha256 header")
+ actual = hashlib.sha256(body).hexdigest()
+ if actual != expected:
+ raise AuditLogDownloadError(f"audit log chunk checksum mismatch (got {actual}, want {expected})")
+
+
+def _parse_chunk_headers(headers: httpx.Headers, current_cursor: str | None) -> tuple[int, str | None, bool]:
+ has_more_value = headers.get("x-has-more")
+ if has_more_value not in {"true", "false"}:
+ raise AuditLogDownloadError("response missing or invalid X-Has-More header")
+ has_more = has_more_value == "true"
+
+ row_count = headers.get("x-row-count")
+ if row_count is None or not row_count.isascii() or not row_count.isdecimal():
+ raise AuditLogDownloadError("response missing or invalid X-Row-Count header")
+ normalized_row_count = row_count.lstrip("0") or "0"
+ if len(normalized_row_count) > len(str(_MAX_CHUNK_ROWS)):
+ raise AuditLogDownloadError("response missing or invalid X-Row-Count header")
+ rows = int(normalized_row_count)
+ if rows > _MAX_CHUNK_ROWS:
+ raise AuditLogDownloadError("response missing or invalid X-Row-Count header")
+
+ next_cursor = headers.get("x-next-cursor") or None
+ if has_more and (not next_cursor or next_cursor == current_cursor):
+ raise AuditLogDownloadError("response has invalid X-Next-Cursor header")
+ if not has_more and next_cursor:
+ raise AuditLogDownloadError("response returned a cursor after the final chunk")
+ return rows, next_cursor, has_more
+
+
+def _write_chunk(destination: BinaryIO, body: bytes) -> None:
+ remaining = memoryview(body)
+ while remaining:
+ written = destination.write(remaining)
+ if type(written) is not int or written <= 0 or written > len(remaining):
+ raise AuditLogDownloadError("audit log download destination performed a short write")
+ remaining = remaining[written:]
+
+
+def _validate_max_transfer_retries(max_transfer_retries: int) -> None:
+ if type(max_transfer_retries) is not int or max_transfer_retries < 0:
+ raise ValueError("max_transfer_retries must be a non-negative integer")
diff --git a/src/kernel/lib/browser_routing/raw_http.py b/src/kernel/lib/browser_routing/raw_http.py
index 5e644e39..3fded8ca 100644
--- a/src/kernel/lib/browser_routing/raw_http.py
+++ b/src/kernel/lib/browser_routing/raw_http.py
@@ -7,7 +7,7 @@
import httpx
from .util import sanitize_curl_raw_params
-from .routing import BrowserRoute
+from .routing import BrowserRoute, mark_direct_vm_headers, direct_vm_request_extensions
from ..._types import Body, Timeout, NotGiven, not_given
from ..._models import FinalRequestOptions
@@ -33,7 +33,7 @@ def request_via_browser_route(
method=method.upper(),
url=route.base_url.rstrip("/") + "/curl/raw",
params=query,
- headers=headers or {},
+ headers=mark_direct_vm_headers(headers),
content=_normalize_binary_content(content),
json_data=json,
timeout=_normalize_timeout(timeout),
@@ -70,6 +70,7 @@ def stream_via_browser_route(
headers=request_headers,
content=_normalize_binary_content(content),
timeout=_normalize_timeout(effective_timeout),
+ extensions=direct_vm_request_extensions(cache=parent.browser_route_cache),
) as response:
yield response
@@ -93,7 +94,7 @@ async def async_request_via_browser_route(
method=method.upper(),
url=route.base_url.rstrip("/") + "/curl/raw",
params=query,
- headers=headers or {},
+ headers=mark_direct_vm_headers(headers),
content=_normalize_binary_content(content),
json_data=json,
timeout=_normalize_timeout(timeout),
@@ -130,6 +131,7 @@ async def async_stream_via_browser_route(
headers=request_headers,
content=_normalize_binary_content(content),
timeout=_normalize_timeout(effective_timeout),
+ extensions=direct_vm_request_extensions(cache=parent.browser_route_cache),
) as response:
yield response
diff --git a/src/kernel/lib/browser_routing/routing.py b/src/kernel/lib/browser_routing/routing.py
index aa84cc1b..61f786cb 100644
--- a/src/kernel/lib/browser_routing/routing.py
+++ b/src/kernel/lib/browser_routing/routing.py
@@ -15,6 +15,7 @@
cdp_ws_url_from_browser_like,
session_id_from_browser_like,
)
+from ..._utils import is_given
from ..._compat import model_copy
from ..._models import FinalRequestOptions
from ..._constants import RAW_RESPONSE_HEADER
@@ -32,6 +33,14 @@ class BrowserRoutingConfig:
subresources: tuple[str, ...] = field(default_factory=tuple)
+_DIRECT_VM_EVICTION_HOOK_ATTR = "_kernel_direct_vm_eviction_hook"
+_DIRECT_VM_AUTH_HOOK_ATTR = "_kernel_direct_vm_auth_hook"
+_DIRECT_VM_REQUEST_MARKER_HEADER = "x-kernel-direct-vm-request"
+_STALE_DIRECT_VM_AUTH_REQUEST_EXTENSION = "kernel_stale_direct_vm_auth"
+_DIRECT_VM_BODY_REPLAYABLE_REQUEST_EXTENSION = "kernel_direct_vm_body_replayable"
+_DIRECT_VM_ROUTE_CACHE_REQUEST_EXTENSION = "kernel_direct_vm_route_cache"
+
+
_BROWSER_ROUTE_CACHEABLE_PATH = re.compile(r"^/(?:v\d+/)?browsers(?:/[^/]+)?/?$")
_BROWSER_DELETE_BY_ID_PATH = re.compile(r"^/(?:v\d+/)?browsers/([^/]+)/?$")
_BROWSER_POOL_ACQUIRE_PATH = re.compile(r"^/(?:v\d+/)?browser_pools/[^/]+/acquire/?$")
@@ -41,7 +50,20 @@ class BrowserRoutingConfig:
def browser_routing_config_from_env() -> BrowserRoutingConfig:
raw = os.environ.get("KERNEL_BROWSER_ROUTING_SUBRESOURCES")
if raw is None:
- return BrowserRoutingConfig(subresources=("curl",))
+ # Path prefixes eligible for direct-to-VM routing. "telemetry/stream" is
+ # the live SSE endpoint (VM); "telemetry/events" is a historical read
+ # served by the control plane (S2) and must NOT be here.
+ return BrowserRoutingConfig(
+ subresources=(
+ "curl",
+ "telemetry/stream",
+ "computer",
+ "playwright",
+ "process",
+ "fs",
+ "logs/stream",
+ )
+ )
if raw.strip() == "":
return BrowserRoutingConfig()
@@ -66,6 +88,14 @@ def set(self, route: BrowserRoute) -> None:
def delete(self, session_id: str) -> None:
self._routes.pop(_normalize_session_id(session_id), None)
+ def delete_if_jwt(self, session_id: str, jwt: str) -> bool:
+ key = _normalize_session_id(session_id)
+ route = self._routes.get(key)
+ if route is None or route.jwt != jwt.strip():
+ return False
+ del self._routes[key]
+ return True
+
def values(self) -> list[BrowserRoute]:
return list(self._routes.values())
@@ -107,14 +137,19 @@ def maybe_populate_browser_route_cache_from_response(response: httpx.Response, *
def maybe_evict_browser_route_from_response(response: httpx.Response, *, cache: BrowserRouteCache) -> None:
- if not response.is_success:
+ if response.is_success:
+ session_id = _session_id_to_evict_from_response(response)
+ if session_id:
+ cache.delete(session_id)
return
- session_id = _session_id_to_evict_from_response(response)
- if not session_id:
+ if not is_stale_direct_vm_auth_response(response):
return
- cache.delete(session_id)
+ jwt = str(response.request.url.params.get("jwt") or "").strip()
+ session_id = _session_id_from_direct_vm_response(response, cache=cache)
+ if session_id and jwt:
+ cache.delete_if_jwt(session_id, jwt)
def populate_browser_route_cache_from_value(value: object, *, cache: BrowserRouteCache) -> None:
@@ -158,6 +193,203 @@ def _session_id_to_evict_from_response(response: httpx.Response) -> str | None:
return None
+def _session_id_from_direct_vm_response(response: httpx.Response, *, cache: BrowserRouteCache) -> str | None:
+ raw = str(response.request.url)
+ for route in cache.values():
+ if raw.startswith(route.base_url.rstrip("/") + "/"):
+ return route.session_id
+ return None
+
+
+def is_stale_direct_vm_auth_response(response: httpx.Response) -> bool:
+ if response.status_code not in {401, 403}:
+ return False
+ return bool(response.request.url.params.get("jwt"))
+
+
+def install_stale_direct_vm_auth_eviction(client: httpx.Client) -> None:
+ """Evict stale direct-to-VM routes as soon as the response status is known.
+
+ httpx reads the body of a non-streamed response inside `send()`, so a caller
+ that only inspects the returned response never learns the status of a 401/403
+ whose body read fails — the read error surfaces from `send()` instead and the
+ dead route would stay cached, wedging every later call for that session. A
+ response event hook runs after the status is known and before any body is
+ read, which keeps eviction independent of the body. It also rejects redirects
+ before httpx can replay an unreplayable direct request body. For a caller-supplied
+ `http_client`, the hook is installed into that client's `event_hooks` and
+ prepended so an existing hook cannot pre-empt these safeguards by reading a
+ failing body or raising.
+ """
+ hooks = client.event_hooks.setdefault("response", [])
+ if any(getattr(hook, _DIRECT_VM_EVICTION_HOOK_ATTR, False) for hook in hooks):
+ return
+
+ def handle_response(response: httpx.Response) -> None:
+ request_cache = _direct_vm_route_cache(response.request)
+ if request_cache is None:
+ return
+ _reject_unreplayable_direct_vm_redirect(response, cache=request_cache)
+ if is_stale_direct_vm_auth_response(response):
+ response.request.extensions[_STALE_DIRECT_VM_AUTH_REQUEST_EXTENSION] = True
+ maybe_evict_browser_route_from_response(response, cache=request_cache)
+
+ setattr(handle_response, _DIRECT_VM_EVICTION_HOOK_ATTR, True)
+ hooks.insert(0, handle_response)
+
+
+def install_async_stale_direct_vm_auth_eviction(client: httpx.AsyncClient) -> None:
+ """Async counterpart of `install_stale_direct_vm_auth_eviction`."""
+ hooks = client.event_hooks.setdefault("response", [])
+ if any(getattr(hook, _DIRECT_VM_EVICTION_HOOK_ATTR, False) for hook in hooks):
+ return
+
+ async def handle_response(response: httpx.Response) -> None:
+ request_cache = _direct_vm_route_cache(response.request)
+ if request_cache is None:
+ return
+ _reject_unreplayable_direct_vm_redirect(response, cache=request_cache)
+ if is_stale_direct_vm_auth_response(response):
+ response.request.extensions[_STALE_DIRECT_VM_AUTH_REQUEST_EXTENSION] = True
+ maybe_evict_browser_route_from_response(response, cache=request_cache)
+
+ setattr(handle_response, _DIRECT_VM_EVICTION_HOOK_ATTR, True)
+ hooks.insert(0, handle_response)
+
+
+def install_direct_vm_auth_stripping(client: httpx.Client) -> None:
+ """Remove Authorization after httpx auth and existing request hooks run."""
+ hooks = client.event_hooks.setdefault("request", [])
+ if any(getattr(hook, _DIRECT_VM_AUTH_HOOK_ATTR, False) for hook in hooks):
+ return
+
+ def strip_auth(request: httpx.Request) -> None:
+ if _is_direct_vm_request(request):
+ request.headers.pop("Authorization", None)
+
+ setattr(strip_auth, _DIRECT_VM_AUTH_HOOK_ATTR, True)
+ hooks.append(strip_auth)
+
+
+def install_async_direct_vm_auth_stripping(client: httpx.AsyncClient) -> None:
+ """Async counterpart of `install_direct_vm_auth_stripping`."""
+ hooks = client.event_hooks.setdefault("request", [])
+ if any(getattr(hook, _DIRECT_VM_AUTH_HOOK_ATTR, False) for hook in hooks):
+ return
+
+ async def strip_auth(request: httpx.Request) -> None:
+ if _is_direct_vm_request(request):
+ request.headers.pop("Authorization", None)
+
+ setattr(strip_auth, _DIRECT_VM_AUTH_HOOK_ATTR, True)
+ hooks.append(strip_auth)
+
+
+def _reject_unreplayable_direct_vm_redirect(response: httpx.Response, *, cache: BrowserRouteCache) -> None:
+ if not response.has_redirect_location or not direct_vm_request_body_is_known_unreplayable(response.request):
+ return
+
+ jwt = str(response.request.url.params.get("jwt") or "").strip()
+ session_id = _session_id_from_direct_vm_response(response, cache=cache)
+ if session_id and jwt:
+ cache.delete_if_jwt(session_id, jwt)
+ raise httpx.RequestError("Cannot safely redirect an unreplayable direct VM request", request=response.request)
+
+
+def should_retry_direct_vm_connection_error(request: httpx.Request) -> bool:
+ """Prevent connection retries from replaying an unreplayable VM request body."""
+ if direct_vm_request_body_is_known_unreplayable(request):
+ return False
+ if not request.extensions.get(_STALE_DIRECT_VM_AUTH_REQUEST_EXTENSION):
+ return True
+ return direct_vm_request_body_is_replayable(request)
+
+
+def should_retry_stale_direct_vm_auth(response: httpx.Response) -> bool:
+ """Whether a stale direct-to-VM auth failure can be retried on the control plane.
+
+ A retry rebuilds the request from the original options, so it is only safe when
+ the body can be serialized again byte for byte. Streamed bodies (e.g. a file
+ object passed to fs.write_file) are consumed by the direct request, so retrying
+ would send a truncated or empty body to the control plane.
+ """
+ if not is_stale_direct_vm_auth_response(response):
+ return False
+ return direct_vm_request_body_is_replayable(response.request)
+
+
+def direct_vm_request_body_is_known_unreplayable(request: httpx.Request) -> bool:
+ return request.extensions.get(_DIRECT_VM_BODY_REPLAYABLE_REQUEST_EXTENSION) is False
+
+
+def direct_vm_request_body_is_replayable(request: httpx.Request) -> bool:
+ replayable = request.extensions.get(_DIRECT_VM_BODY_REPLAYABLE_REQUEST_EXTENSION)
+ if isinstance(replayable, bool):
+ return replayable
+ return _classify_direct_vm_request_body_replayability(request)
+
+
+def _classify_direct_vm_request_body_replayability(request: httpx.Request) -> bool:
+ try:
+ _ = request.content
+ except httpx.RequestNotRead:
+ pass
+ else:
+ return True
+
+ # httpx encodes multipart bodies as a stream of fields it re-renders per attempt.
+ fields = getattr(request.stream, "fields", None)
+ if fields is None:
+ # A streamed body (file object, iterator or async iterator) cannot be replayed.
+ return False
+ return all(_multipart_field_is_replayable(field) for field in cast("list[Any]", fields))
+
+
+def _multipart_field_is_replayable(field: Any) -> bool:
+ file = getattr(field, "file", None)
+ if file is None:
+ # A data field renders from an in-memory value.
+ return True
+ if isinstance(file, (bytes, str)):
+ return True
+ if getattr(file, "closed", False):
+ return False
+ return _rewind_succeeds(file)
+
+
+def _rewind_succeeds(file: Any) -> bool:
+ """Whether the file field can actually be rewound for another render.
+
+ `seekable()` is not proof: a wrapper can report True and still raise from
+ `seek()`, which would render the field as an empty part on the retry. The
+ only reliable check is to perform the rewind httpx would perform.
+ """
+ seek = getattr(file, "seek", None)
+ if not callable(seek):
+ return False
+
+ position: object = None
+ tell = getattr(file, "tell", None)
+ if callable(tell):
+ try:
+ position = tell()
+ except Exception:
+ position = None
+
+ try:
+ seek(0)
+ except Exception:
+ return False
+
+ if isinstance(position, int) and position > 0:
+ try:
+ seek(position)
+ except Exception:
+ # The field is left rewound, which is where httpx renders it from anyway.
+ pass
+ return True
+
+
def _session_id_from_browser_delete_path(path: str) -> str | None:
match = _BROWSER_DELETE_BY_ID_PATH.match(path)
if match is None:
@@ -188,6 +420,21 @@ def _session_id_from_browser_pool_release_request(request: httpx.Request, path:
return normalized or None
+def _matches_direct_vm_prefix(tail: str, prefixes: tuple[str, ...]) -> bool:
+ """Whether tail (the path after browsers/{id}/) is covered by an allow prefix,
+ matching on segment boundaries: "telemetry/stream" matches "telemetry/stream"
+ and "telemetry/stream/...", but not "telemetry/events" or "telemetry/streamfoo".
+ Keeps historical control-plane reads (e.g. telemetry/events, served from S2)
+ off the VM.
+ """
+ tail = tail.strip("/")
+ for prefix in prefixes:
+ prefix = prefix.strip("/")
+ if prefix and (tail == prefix or tail.startswith(prefix + "/")):
+ return True
+ return False
+
+
def rewrite_direct_vm_options(
options: FinalRequestOptions,
*,
@@ -199,7 +446,7 @@ def rewrite_direct_vm_options(
return options
session_id, subresource, suffix = match
- if subresource not in set(config.subresources):
+ if not _matches_direct_vm_prefix(f"{subresource}{suffix}", config.subresources):
return options
route = cache.get(session_id)
@@ -213,15 +460,45 @@ def rewrite_direct_vm_options(
params.update(options.params)
params["jwt"] = route.jwt
rewritten.params = params or options.params
+
+ headers = dict(options.headers) if is_given(options.headers) else {}
+ headers[_DIRECT_VM_REQUEST_MARKER_HEADER] = "true"
+ rewritten.headers = headers
return rewritten
-def strip_direct_vm_auth(request: httpx.Request, *, cache: BrowserRouteCache) -> None:
- raw = str(request.url)
- for route in cache.values():
- if raw.startswith(route.base_url.rstrip("/") + "/"):
- request.headers.pop("Authorization", None)
- return
+def mark_direct_vm_headers(headers: Mapping[str, str] | None) -> dict[str, str]:
+ marked = dict(headers or {})
+ marked[_DIRECT_VM_REQUEST_MARKER_HEADER] = "true"
+ return marked
+
+
+def prepare_direct_vm_request(request: httpx.Request, *, cache: BrowserRouteCache) -> None:
+ if request.headers.pop(_DIRECT_VM_REQUEST_MARKER_HEADER, None) is None:
+ return
+
+ # Request hooks and custom auth can buffer this request while consuming
+ # the original body that the SDK would use to build a retry.
+ request.extensions[_DIRECT_VM_BODY_REPLAYABLE_REQUEST_EXTENSION] = _classify_direct_vm_request_body_replayability(
+ request
+ )
+ request.extensions[_DIRECT_VM_ROUTE_CACHE_REQUEST_EXTENSION] = cache
+ request.headers.pop("Authorization", None)
+
+
+def _direct_vm_route_cache(request: httpx.Request) -> BrowserRouteCache | None:
+ cache = request.extensions.get(_DIRECT_VM_ROUTE_CACHE_REQUEST_EXTENSION)
+ return cache if isinstance(cache, BrowserRouteCache) else None
+
+
+def direct_vm_request_extensions(*, cache: BrowserRouteCache) -> dict[str, object]:
+ return {_DIRECT_VM_ROUTE_CACHE_REQUEST_EXTENSION: cache}
+
+
+def _is_direct_vm_request(request: httpx.Request) -> bool:
+ return _direct_vm_route_cache(request) is not None or isinstance(
+ request.extensions.get(_DIRECT_VM_BODY_REPLAYABLE_REQUEST_EXTENSION), bool
+ )
def match_direct_vm_path(path: str) -> tuple[str, str, str] | None:
diff --git a/src/kernel/lib/config_registry_wait.py b/src/kernel/lib/config_registry_wait.py
new file mode 100644
index 00000000..fbf399a8
--- /dev/null
+++ b/src/kernel/lib/config_registry_wait.py
@@ -0,0 +1,63 @@
+from __future__ import annotations
+
+import math
+import time
+import random
+from datetime import datetime
+
+from .._types import Omit, Headers
+from .._exceptions import KernelError
+from ..types.config_registry_response import ConfigRegistryResponse
+
+DEFAULT_CONFIG_REGISTRY_POLL_INTERVAL = 5.0
+_TERMINAL_STATUSES = frozenset({"completed", "failed", "canceled", "expired"})
+
+
+def validate_wait_options(poll_interval: float, max_wait_seconds: float | None) -> None:
+ if not math.isfinite(poll_interval) or poll_interval <= 0:
+ raise ValueError("Expected a finite, positive value for `poll_interval`")
+ if max_wait_seconds is not None and (not math.isfinite(max_wait_seconds) or max_wait_seconds < 0):
+ raise ValueError("Expected a finite, non-negative value for `max_wait_seconds`")
+
+
+def poll_headers(extra_headers: Headers | None) -> Headers:
+ headers: dict[str, str | Omit] = dict(extra_headers or {})
+ headers["X-Stainless-Poll-Helper"] = "true"
+ return headers
+
+
+def poll_delay(poll_interval: float) -> float:
+ return poll_interval * random.uniform(0.9, 1.1)
+
+
+def wait_timeout_error(id: str, polls: int, last_status: str | None, started_at: float) -> TimeoutError:
+ elapsed = time.monotonic() - started_at
+ return TimeoutError(
+ f"Timed out waiting for config registry analysis {id!r} after {elapsed:.1f}s "
+ f"and {polls} polls; last status was {last_status!r}"
+ )
+
+
+def analysis_finished(response: ConfigRegistryResponse, requested_id: str) -> tuple[bool, str]:
+ analysis = response.analysis
+ if analysis is None:
+ raise KernelError(f"Config registry response for {requested_id!r} is missing an analysis")
+
+ analysis_id = getattr(analysis, "id", None)
+ if not isinstance(analysis_id, str) or not analysis_id:
+ raise KernelError(f"Config registry response for {requested_id!r} has no valid analysis ID")
+ if analysis_id != requested_id:
+ raise KernelError(f"Config registry response for {requested_id!r} returned analysis {analysis_id!r}")
+
+ status = getattr(analysis, "status", None)
+ if not isinstance(status, str) or not status:
+ raise KernelError(f"Config registry analysis {requested_id!r} has no valid status")
+
+ if "finished_at" not in analysis.model_fields_set:
+ raise KernelError(f"Config registry analysis {requested_id!r} is missing `finished_at`")
+
+ finished_at = getattr(analysis, "finished_at", None)
+ if finished_at is not None and not isinstance(finished_at, datetime):
+ raise KernelError(f"Config registry analysis {requested_id!r} has an invalid `finished_at`")
+
+ return finished_at is not None or status in _TERMINAL_STATUSES, status
diff --git a/src/kernel/lib/multipart.py b/src/kernel/lib/multipart.py
new file mode 100644
index 00000000..611c6d8c
--- /dev/null
+++ b/src/kernel/lib/multipart.py
@@ -0,0 +1,41 @@
+from __future__ import annotations
+
+from typing import Mapping, Sequence, cast
+
+from .._utils import is_given
+
+__all__ = ["indexed_multipart_body"]
+
+
+def indexed_multipart_body(body: object) -> dict[str, object]:
+ """Flatten a multipart body so that array entries carry their index.
+
+ Endpoints that take an array of objects with a file field need each entry's
+ fields grouped together: `files[0][dest_path]` pairs with the `files[0][file]`
+ part, while repeated `files[][dest_path]` names cannot be matched back to
+ their file. The returned mapping is already flat, so the client's generic
+ multipart serialization passes the names through untouched and every other
+ endpoint keeps its existing encoding.
+ """
+ flattened: dict[str, object] = {}
+ if isinstance(body, Mapping):
+ for key, value in cast(Mapping[object, object], body).items():
+ _flatten(str(key), value, flattened)
+ return flattened
+
+
+def _flatten(key: str, value: object, out: dict[str, object]) -> None:
+ if not is_given(value):
+ return
+
+ if isinstance(value, Mapping):
+ for child_key, child in cast(Mapping[object, object], value).items():
+ _flatten(f"{key}[{child_key}]", child, out)
+ return
+
+ if isinstance(value, (list, tuple)):
+ for index, child in enumerate(cast(Sequence[object], value)):
+ _flatten(f"{key}[{index}]", child, out)
+ return
+
+ out[key] = value
diff --git a/src/kernel/pagination.py b/src/kernel/pagination.py
index cdf83c2f..98152da2 100644
--- a/src/kernel/pagination.py
+++ b/src/kernel/pagination.py
@@ -9,13 +9,93 @@
from ._models import BaseModel
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
-__all__ = ["SyncOffsetPagination", "AsyncOffsetPagination"]
+__all__ = ["SyncPageTokenPagination", "AsyncPageTokenPagination", "SyncOffsetPagination", "AsyncOffsetPagination"]
_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
_T = TypeVar("_T")
+class SyncPageTokenPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+ items: List[_T]
+ next_page_token: Optional[str] = None
+ has_more: Optional[bool] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ items = self.items
+ if not items:
+ return []
+ return items
+
+ @override
+ def has_next_page(self) -> bool:
+ has_more = self.has_more
+ if has_more is not None and has_more is False:
+ return False
+
+ return super().has_next_page()
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next_page_token = self.next_page_token
+ if not next_page_token:
+ return None
+
+ return PageInfo(params={"page_token": next_page_token})
+
+ @classmethod
+ def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
+ return cls.construct(
+ None,
+ **{
+ **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+ "next_page_token": response.headers.get("X-Next-Page-Token"),
+ "has_more": maybe_coerce_boolean(response.headers.get("X-Has-More")),
+ },
+ )
+
+
+class AsyncPageTokenPagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+ items: List[_T]
+ next_page_token: Optional[str] = None
+ has_more: Optional[bool] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ items = self.items
+ if not items:
+ return []
+ return items
+
+ @override
+ def has_next_page(self) -> bool:
+ has_more = self.has_more
+ if has_more is not None and has_more is False:
+ return False
+
+ return super().has_next_page()
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next_page_token = self.next_page_token
+ if not next_page_token:
+ return None
+
+ return PageInfo(params={"page_token": next_page_token})
+
+ @classmethod
+ def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
+ return cls.construct(
+ None,
+ **{
+ **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+ "next_page_token": response.headers.get("X-Next-Page-Token"),
+ "has_more": maybe_coerce_boolean(response.headers.get("X-Has-More")),
+ },
+ )
+
+
class SyncOffsetPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
items: List[_T]
has_more: Optional[bool] = None
@@ -40,12 +120,20 @@ def has_next_page(self) -> bool:
def next_page_info(self) -> Optional[PageInfo]:
next_offset = self.next_offset
if next_offset is None:
- return None # type: ignore[unreachable]
+ if self.has_more: # type: ignore[unreachable]
+ raise RuntimeError(
+ "Server reported X-Has-More: true without an X-Next-Offset header; "
+ "refusing to silently truncate pagination"
+ )
+ return None
- length = len(self._get_page_items())
- current_count = next_offset + length
+ # X-Next-Offset is the next page's absolute start, or 0 on the last page
+ # (the API's stop sentinel). Only a positive offset advances; the old code
+ # added the current page length on top, skipping a page per iteration.
+ if next_offset == 0:
+ return None
- return PageInfo(params={"offset": current_count})
+ return PageInfo(params={"offset": next_offset})
@classmethod
def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
@@ -83,12 +171,20 @@ def has_next_page(self) -> bool:
def next_page_info(self) -> Optional[PageInfo]:
next_offset = self.next_offset
if next_offset is None:
- return None # type: ignore[unreachable]
-
- length = len(self._get_page_items())
- current_count = next_offset + length
-
- return PageInfo(params={"offset": current_count})
+ if self.has_more: # type: ignore[unreachable]
+ raise RuntimeError(
+ "Server reported X-Has-More: true without an X-Next-Offset header; "
+ "refusing to silently truncate pagination"
+ )
+ return None
+
+ # X-Next-Offset is the next page's absolute start, or 0 on the last page
+ # (the API's stop sentinel). Only a positive offset advances; the old code
+ # added the current page length on top, skipping a page per iteration.
+ if next_offset == 0:
+ return None
+
+ return PageInfo(params={"offset": next_offset})
@classmethod
def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
diff --git a/src/kernel/resources/__init__.py b/src/kernel/resources/__init__.py
index f078a03b..4be8e2ba 100644
--- a/src/kernel/resources/__init__.py
+++ b/src/kernel/resources/__init__.py
@@ -16,6 +16,22 @@
AuthResourceWithStreamingResponse,
AsyncAuthResourceWithStreamingResponse,
)
+from .search import (
+ SearchResource,
+ AsyncSearchResource,
+ SearchResourceWithRawResponse,
+ AsyncSearchResourceWithRawResponse,
+ SearchResourceWithStreamingResponse,
+ AsyncSearchResourceWithStreamingResponse,
+)
+from .vaults import (
+ VaultsResource,
+ AsyncVaultsResource,
+ VaultsResourceWithRawResponse,
+ AsyncVaultsResourceWithRawResponse,
+ VaultsResourceWithStreamingResponse,
+ AsyncVaultsResourceWithStreamingResponse,
+)
from .proxies import (
ProxiesResource,
AsyncProxiesResource,
@@ -24,6 +40,14 @@
ProxiesResourceWithStreamingResponse,
AsyncProxiesResourceWithStreamingResponse,
)
+from .api_keys import (
+ APIKeysResource,
+ AsyncAPIKeysResource,
+ APIKeysResourceWithRawResponse,
+ AsyncAPIKeysResourceWithRawResponse,
+ APIKeysResourceWithStreamingResponse,
+ AsyncAPIKeysResourceWithStreamingResponse,
+)
from .browsers import (
BrowsersResource,
AsyncBrowsersResource,
@@ -48,6 +72,22 @@
ProjectsResourceWithStreamingResponse,
AsyncProjectsResourceWithStreamingResponse,
)
+from .telemetry import (
+ TelemetryResource,
+ AsyncTelemetryResource,
+ TelemetryResourceWithRawResponse,
+ AsyncTelemetryResourceWithRawResponse,
+ TelemetryResourceWithStreamingResponse,
+ AsyncTelemetryResourceWithStreamingResponse,
+)
+from .audit_logs import (
+ AuditLogsResource,
+ AsyncAuditLogsResource,
+ AuditLogsResourceWithRawResponse,
+ AsyncAuditLogsResourceWithRawResponse,
+ AuditLogsResourceWithStreamingResponse,
+ AsyncAuditLogsResourceWithStreamingResponse,
+)
from .extensions import (
ExtensionsResource,
AsyncExtensionsResource,
@@ -80,6 +120,14 @@
InvocationsResourceWithStreamingResponse,
AsyncInvocationsResourceWithStreamingResponse,
)
+from .organization import (
+ OrganizationResource,
+ AsyncOrganizationResource,
+ OrganizationResourceWithRawResponse,
+ AsyncOrganizationResourceWithRawResponse,
+ OrganizationResourceWithStreamingResponse,
+ AsyncOrganizationResourceWithStreamingResponse,
+)
from .browser_pools import (
BrowserPoolsResource,
AsyncBrowserPoolsResource,
@@ -88,6 +136,14 @@
BrowserPoolsResourceWithStreamingResponse,
AsyncBrowserPoolsResourceWithStreamingResponse,
)
+from .config_registry import (
+ ConfigRegistryResource,
+ AsyncConfigRegistryResource,
+ ConfigRegistryResourceWithRawResponse,
+ AsyncConfigRegistryResourceWithRawResponse,
+ ConfigRegistryResourceWithStreamingResponse,
+ AsyncConfigRegistryResourceWithStreamingResponse,
+)
from .credential_providers import (
CredentialProvidersResource,
AsyncCredentialProvidersResource,
@@ -96,6 +152,14 @@
CredentialProvidersResourceWithStreamingResponse,
AsyncCredentialProvidersResourceWithStreamingResponse,
)
+from .vault_provider_configs import (
+ VaultProviderConfigsResource,
+ AsyncVaultProviderConfigsResource,
+ VaultProviderConfigsResourceWithRawResponse,
+ AsyncVaultProviderConfigsResourceWithRawResponse,
+ VaultProviderConfigsResourceWithStreamingResponse,
+ AsyncVaultProviderConfigsResourceWithStreamingResponse,
+)
__all__ = [
"DeploymentsResource",
@@ -116,6 +180,12 @@
"AsyncInvocationsResourceWithRawResponse",
"InvocationsResourceWithStreamingResponse",
"AsyncInvocationsResourceWithStreamingResponse",
+ "ConfigRegistryResource",
+ "AsyncConfigRegistryResource",
+ "ConfigRegistryResourceWithRawResponse",
+ "AsyncConfigRegistryResourceWithRawResponse",
+ "ConfigRegistryResourceWithStreamingResponse",
+ "AsyncConfigRegistryResourceWithStreamingResponse",
"BrowsersResource",
"AsyncBrowsersResource",
"BrowsersResourceWithRawResponse",
@@ -134,6 +204,12 @@
"AsyncAuthResourceWithRawResponse",
"AuthResourceWithStreamingResponse",
"AsyncAuthResourceWithStreamingResponse",
+ "TelemetryResource",
+ "AsyncTelemetryResource",
+ "TelemetryResourceWithRawResponse",
+ "AsyncTelemetryResourceWithRawResponse",
+ "TelemetryResourceWithStreamingResponse",
+ "AsyncTelemetryResourceWithStreamingResponse",
"ProxiesResource",
"AsyncProxiesResource",
"ProxiesResourceWithRawResponse",
@@ -152,6 +228,18 @@
"AsyncBrowserPoolsResourceWithRawResponse",
"BrowserPoolsResourceWithStreamingResponse",
"AsyncBrowserPoolsResourceWithStreamingResponse",
+ "VaultProviderConfigsResource",
+ "AsyncVaultProviderConfigsResource",
+ "VaultProviderConfigsResourceWithRawResponse",
+ "AsyncVaultProviderConfigsResourceWithRawResponse",
+ "VaultProviderConfigsResourceWithStreamingResponse",
+ "AsyncVaultProviderConfigsResourceWithStreamingResponse",
+ "VaultsResource",
+ "AsyncVaultsResource",
+ "VaultsResourceWithRawResponse",
+ "AsyncVaultsResourceWithRawResponse",
+ "VaultsResourceWithStreamingResponse",
+ "AsyncVaultsResourceWithStreamingResponse",
"CredentialsResource",
"AsyncCredentialsResource",
"CredentialsResourceWithRawResponse",
@@ -164,10 +252,34 @@
"AsyncProjectsResourceWithRawResponse",
"ProjectsResourceWithStreamingResponse",
"AsyncProjectsResourceWithStreamingResponse",
+ "OrganizationResource",
+ "AsyncOrganizationResource",
+ "OrganizationResourceWithRawResponse",
+ "AsyncOrganizationResourceWithRawResponse",
+ "OrganizationResourceWithStreamingResponse",
+ "AsyncOrganizationResourceWithStreamingResponse",
+ "AuditLogsResource",
+ "AsyncAuditLogsResource",
+ "AuditLogsResourceWithRawResponse",
+ "AsyncAuditLogsResourceWithRawResponse",
+ "AuditLogsResourceWithStreamingResponse",
+ "AsyncAuditLogsResourceWithStreamingResponse",
+ "APIKeysResource",
+ "AsyncAPIKeysResource",
+ "APIKeysResourceWithRawResponse",
+ "AsyncAPIKeysResourceWithRawResponse",
+ "APIKeysResourceWithStreamingResponse",
+ "AsyncAPIKeysResourceWithStreamingResponse",
"CredentialProvidersResource",
"AsyncCredentialProvidersResource",
"CredentialProvidersResourceWithRawResponse",
"AsyncCredentialProvidersResourceWithRawResponse",
"CredentialProvidersResourceWithStreamingResponse",
"AsyncCredentialProvidersResourceWithStreamingResponse",
+ "SearchResource",
+ "AsyncSearchResource",
+ "SearchResourceWithRawResponse",
+ "AsyncSearchResourceWithRawResponse",
+ "SearchResourceWithStreamingResponse",
+ "AsyncSearchResourceWithStreamingResponse",
]
diff --git a/src/kernel/resources/api_keys.py b/src/kernel/resources/api_keys.py
new file mode 100644
index 00000000..52e6e6f9
--- /dev/null
+++ b/src/kernel/resources/api_keys.py
@@ -0,0 +1,791 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import (
+ api_key_list_params,
+ api_key_create_params,
+ api_key_rotate_params,
+ api_key_update_params,
+ api_key_retrieve_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.api_key import APIKey
+from ..types.created_api_key import CreatedAPIKey
+
+__all__ = ["APIKeysResource", "AsyncAPIKeysResource"]
+
+
+class APIKeysResource(SyncAPIResource):
+ """Create and manage API keys for organization and project-scoped access."""
+
+ @cached_property
+ def with_raw_response(self) -> APIKeysResourceWithRawResponse:
+ """
+ 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 APIKeysResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> APIKeysResourceWithStreamingResponse:
+ """
+ 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 APIKeysResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ name: str,
+ days_to_expire: Optional[int] | Omit = omit,
+ expires_at: Union[str, datetime, None] | Omit = omit,
+ policy: api_key_create_params.Policy | Omit = omit,
+ project_id: Optional[str] | 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,
+ ) -> CreatedAPIKey:
+ """Create a new API key.
+
+ API keys may be scoped by project and may include a
+ policy.
+
+ Args:
+ name: Label for the API key (1-255 characters). API keys are not addressable by name.
+
+ days_to_expire: Number of days until expiry, up to 3650. Use null for never. Cannot be combined
+ with expires_at.
+
+ expires_at: Exact expiry time. Must be in the future and no later than the issuing
+ credential's own expiry. Cannot be combined with days_to_expire.
+
+ policy: Issue a scoped key that may only perform the listed actions on the listed
+ resources.
+
+ project_id: Unique project identifier
+
+ 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(
+ "/org/api_keys",
+ body=maybe_transform(
+ {
+ "name": name,
+ "days_to_expire": days_to_expire,
+ "expires_at": expires_at,
+ "policy": policy,
+ "project_id": project_id,
+ },
+ api_key_create_params.APIKeyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreatedAPIKey,
+ )
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ include_deleted: bool | 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,
+ ) -> APIKey:
+ """Retrieve an API key by ID for the authenticated organization.
+
+ API keys are
+ masked.
+
+ Args:
+ include_deleted: When true, return the API key even if it has been deleted (soft-deleted), for
+ audit purposes. Defaults to false, which returns 404 for a deleted key.
+
+ 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("/org/api_keys/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"include_deleted": include_deleted}, api_key_retrieve_params.APIKeyRetrieveParams
+ ),
+ ),
+ cast_to=APIKey,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ 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,
+ ) -> APIKey:
+ """
+ Update an API key's name.
+
+ Args:
+ name: New API key name
+
+ 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("/org/api_keys/{id}", id=id),
+ body=maybe_transform({"name": name}, api_key_update_params.APIKeyUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=APIKey,
+ )
+
+ def list(
+ self,
+ *,
+ include_deleted: bool | Omit = omit,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | Omit = omit,
+ sort_by: Literal["created_at", "name", "expires_at"] | Omit = omit,
+ sort_direction: Literal["asc", "desc"] | Omit = omit,
+ status: Literal["active", "deleted", "all"] | 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[APIKey]:
+ """List API keys for the authenticated organization.
+
+ API keys are masked.
+
+ Args:
+ include_deleted: Deprecated: use status=all instead. When true, include deleted (soft-deleted)
+ API keys in the results for audit purposes.
+
+ limit: Maximum number of results to return
+
+ name: Exact-match filter on API key name using the database collation. In production,
+ matching is case- and accent-insensitive. Names are not required to be unique,
+ so multiple keys may match. When status=all or include_deleted=true is set,
+ soft-deleted keys with the same name may also match.
+
+ offset: Number of results to skip
+
+ query: Case-insensitive substring match against API key name, creator, and project. API
+ key identifiers and masked keys match by exact value or prefix.
+
+ sort_by: Field to sort API keys by.
+
+ sort_direction: Sort direction for API keys.
+
+ status: Filter API keys by status. "active" returns keys that are not deleted (default;
+ expired-but-not-deleted keys are still included), "deleted" returns only
+ soft-deleted keys, "all" returns both.
+
+ 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(
+ "/org/api_keys",
+ page=SyncOffsetPagination[APIKey],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "include_deleted": include_deleted,
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ "sort_by": sort_by,
+ "sort_direction": sort_direction,
+ "status": status,
+ },
+ api_key_list_params.APIKeyListParams,
+ ),
+ ),
+ model=APIKey,
+ )
+
+ 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:
+ """Delete an API key.
+
+ A key cannot delete itself; use a different key to delete
+ this one.
+
+ 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("/org/api_keys/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def rotate(
+ self,
+ id: str,
+ *,
+ days_to_expire: Optional[int] | Omit = omit,
+ expire_in_days: 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,
+ ) -> CreatedAPIKey:
+ """Rotate an API key.
+
+ Issues a new key that copies the name and project of the
+ rotated key, and schedules the rotated key to expire after a grace period so
+ in-flight callers can swap over. The new plaintext key is returned once.
+
+ Args:
+ days_to_expire: Lifetime in days for the new key, up to 3650. Omit to reuse the rotated key's
+ original lifetime, or never-expires if it had none.
+
+ expire_in_days: Grace period in days before the rotated key expires. Use 0 to expire it
+ immediately. Omit for the default grace period of 7 days.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/org/api_keys/{id}/rotate", id=id),
+ body=maybe_transform(
+ {
+ "days_to_expire": days_to_expire,
+ "expire_in_days": expire_in_days,
+ },
+ api_key_rotate_params.APIKeyRotateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreatedAPIKey,
+ )
+
+
+class AsyncAPIKeysResource(AsyncAPIResource):
+ """Create and manage API keys for organization and project-scoped access."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncAPIKeysResourceWithRawResponse:
+ """
+ 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 AsyncAPIKeysResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAPIKeysResourceWithStreamingResponse:
+ """
+ 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 AsyncAPIKeysResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ name: str,
+ days_to_expire: Optional[int] | Omit = omit,
+ expires_at: Union[str, datetime, None] | Omit = omit,
+ policy: api_key_create_params.Policy | Omit = omit,
+ project_id: Optional[str] | 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,
+ ) -> CreatedAPIKey:
+ """Create a new API key.
+
+ API keys may be scoped by project and may include a
+ policy.
+
+ Args:
+ name: Label for the API key (1-255 characters). API keys are not addressable by name.
+
+ days_to_expire: Number of days until expiry, up to 3650. Use null for never. Cannot be combined
+ with expires_at.
+
+ expires_at: Exact expiry time. Must be in the future and no later than the issuing
+ credential's own expiry. Cannot be combined with days_to_expire.
+
+ policy: Issue a scoped key that may only perform the listed actions on the listed
+ resources.
+
+ project_id: Unique project identifier
+
+ 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(
+ "/org/api_keys",
+ body=await async_maybe_transform(
+ {
+ "name": name,
+ "days_to_expire": days_to_expire,
+ "expires_at": expires_at,
+ "policy": policy,
+ "project_id": project_id,
+ },
+ api_key_create_params.APIKeyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreatedAPIKey,
+ )
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ include_deleted: bool | 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,
+ ) -> APIKey:
+ """Retrieve an API key by ID for the authenticated organization.
+
+ API keys are
+ masked.
+
+ Args:
+ include_deleted: When true, return the API key even if it has been deleted (soft-deleted), for
+ audit purposes. Defaults to false, which returns 404 for a deleted key.
+
+ 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("/org/api_keys/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"include_deleted": include_deleted}, api_key_retrieve_params.APIKeyRetrieveParams
+ ),
+ ),
+ cast_to=APIKey,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ 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,
+ ) -> APIKey:
+ """
+ Update an API key's name.
+
+ Args:
+ name: New API key name
+
+ 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("/org/api_keys/{id}", id=id),
+ body=await async_maybe_transform({"name": name}, api_key_update_params.APIKeyUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=APIKey,
+ )
+
+ def list(
+ self,
+ *,
+ include_deleted: bool | Omit = omit,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | Omit = omit,
+ sort_by: Literal["created_at", "name", "expires_at"] | Omit = omit,
+ sort_direction: Literal["asc", "desc"] | Omit = omit,
+ status: Literal["active", "deleted", "all"] | 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[APIKey, AsyncOffsetPagination[APIKey]]:
+ """List API keys for the authenticated organization.
+
+ API keys are masked.
+
+ Args:
+ include_deleted: Deprecated: use status=all instead. When true, include deleted (soft-deleted)
+ API keys in the results for audit purposes.
+
+ limit: Maximum number of results to return
+
+ name: Exact-match filter on API key name using the database collation. In production,
+ matching is case- and accent-insensitive. Names are not required to be unique,
+ so multiple keys may match. When status=all or include_deleted=true is set,
+ soft-deleted keys with the same name may also match.
+
+ offset: Number of results to skip
+
+ query: Case-insensitive substring match against API key name, creator, and project. API
+ key identifiers and masked keys match by exact value or prefix.
+
+ sort_by: Field to sort API keys by.
+
+ sort_direction: Sort direction for API keys.
+
+ status: Filter API keys by status. "active" returns keys that are not deleted (default;
+ expired-but-not-deleted keys are still included), "deleted" returns only
+ soft-deleted keys, "all" returns both.
+
+ 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(
+ "/org/api_keys",
+ page=AsyncOffsetPagination[APIKey],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "include_deleted": include_deleted,
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ "sort_by": sort_by,
+ "sort_direction": sort_direction,
+ "status": status,
+ },
+ api_key_list_params.APIKeyListParams,
+ ),
+ ),
+ model=APIKey,
+ )
+
+ 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:
+ """Delete an API key.
+
+ A key cannot delete itself; use a different key to delete
+ this one.
+
+ 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("/org/api_keys/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def rotate(
+ self,
+ id: str,
+ *,
+ days_to_expire: Optional[int] | Omit = omit,
+ expire_in_days: 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,
+ ) -> CreatedAPIKey:
+ """Rotate an API key.
+
+ Issues a new key that copies the name and project of the
+ rotated key, and schedules the rotated key to expire after a grace period so
+ in-flight callers can swap over. The new plaintext key is returned once.
+
+ Args:
+ days_to_expire: Lifetime in days for the new key, up to 3650. Omit to reuse the rotated key's
+ original lifetime, or never-expires if it had none.
+
+ expire_in_days: Grace period in days before the rotated key expires. Use 0 to expire it
+ immediately. Omit for the default grace period of 7 days.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/org/api_keys/{id}/rotate", id=id),
+ body=await async_maybe_transform(
+ {
+ "days_to_expire": days_to_expire,
+ "expire_in_days": expire_in_days,
+ },
+ api_key_rotate_params.APIKeyRotateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreatedAPIKey,
+ )
+
+
+class APIKeysResourceWithRawResponse:
+ def __init__(self, api_keys: APIKeysResource) -> None:
+ self._api_keys = api_keys
+
+ self.create = to_raw_response_wrapper(
+ api_keys.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ api_keys.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ api_keys.update,
+ )
+ self.list = to_raw_response_wrapper(
+ api_keys.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ api_keys.delete,
+ )
+ self.rotate = to_raw_response_wrapper(
+ api_keys.rotate,
+ )
+
+
+class AsyncAPIKeysResourceWithRawResponse:
+ def __init__(self, api_keys: AsyncAPIKeysResource) -> None:
+ self._api_keys = api_keys
+
+ self.create = async_to_raw_response_wrapper(
+ api_keys.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ api_keys.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ api_keys.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ api_keys.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ api_keys.delete,
+ )
+ self.rotate = async_to_raw_response_wrapper(
+ api_keys.rotate,
+ )
+
+
+class APIKeysResourceWithStreamingResponse:
+ def __init__(self, api_keys: APIKeysResource) -> None:
+ self._api_keys = api_keys
+
+ self.create = to_streamed_response_wrapper(
+ api_keys.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ api_keys.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ api_keys.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ api_keys.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ api_keys.delete,
+ )
+ self.rotate = to_streamed_response_wrapper(
+ api_keys.rotate,
+ )
+
+
+class AsyncAPIKeysResourceWithStreamingResponse:
+ def __init__(self, api_keys: AsyncAPIKeysResource) -> None:
+ self._api_keys = api_keys
+
+ self.create = async_to_streamed_response_wrapper(
+ api_keys.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ api_keys.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ api_keys.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ api_keys.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ api_keys.delete,
+ )
+ self.rotate = async_to_streamed_response_wrapper(
+ api_keys.rotate,
+ )
diff --git a/src/kernel/resources/audit_logs/__init__.py b/src/kernel/resources/audit_logs/__init__.py
new file mode 100644
index 00000000..5127038d
--- /dev/null
+++ b/src/kernel/resources/audit_logs/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .audit_logs import (
+ AuditLogsResource,
+ AsyncAuditLogsResource,
+ AuditLogsResourceWithRawResponse,
+ AsyncAuditLogsResourceWithRawResponse,
+ AuditLogsResourceWithStreamingResponse,
+ AsyncAuditLogsResourceWithStreamingResponse,
+)
+from .export_destinations import (
+ ExportDestinationsResource,
+ AsyncExportDestinationsResource,
+ ExportDestinationsResourceWithRawResponse,
+ AsyncExportDestinationsResourceWithRawResponse,
+ ExportDestinationsResourceWithStreamingResponse,
+ AsyncExportDestinationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ExportDestinationsResource",
+ "AsyncExportDestinationsResource",
+ "ExportDestinationsResourceWithRawResponse",
+ "AsyncExportDestinationsResourceWithRawResponse",
+ "ExportDestinationsResourceWithStreamingResponse",
+ "AsyncExportDestinationsResourceWithStreamingResponse",
+ "AuditLogsResource",
+ "AsyncAuditLogsResource",
+ "AuditLogsResourceWithRawResponse",
+ "AsyncAuditLogsResourceWithRawResponse",
+ "AuditLogsResourceWithStreamingResponse",
+ "AsyncAuditLogsResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/audit_logs/audit_logs.py b/src/kernel/resources/audit_logs/audit_logs.py
new file mode 100644
index 00000000..d6aa80a6
--- /dev/null
+++ b/src/kernel/resources/audit_logs/audit_logs.py
@@ -0,0 +1,617 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, BinaryIO, ContextManager, AsyncContextManager
+from datetime import datetime
+from typing_extensions import Literal
+
+import httpx
+
+from ...types import audit_log_list_params, audit_log_export_chunk_params
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ BinaryAPIResponse,
+ AsyncBinaryAPIResponse,
+ StreamedBinaryAPIResponse,
+ AsyncStreamedBinaryAPIResponse,
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ to_custom_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+ to_custom_streamed_response_wrapper,
+ async_to_custom_raw_response_wrapper,
+ async_to_custom_streamed_response_wrapper,
+)
+from ...pagination import SyncPageTokenPagination, AsyncPageTokenPagination
+from ..._base_client import AsyncPaginator, make_request_options
+from .export_destinations import (
+ ExportDestinationsResource,
+ AsyncExportDestinationsResource,
+ ExportDestinationsResourceWithRawResponse,
+ AsyncExportDestinationsResourceWithRawResponse,
+ ExportDestinationsResourceWithStreamingResponse,
+ AsyncExportDestinationsResourceWithStreamingResponse,
+)
+from ...types.audit_log_entry import AuditLogEntry
+from ...lib.audit_log_download import (
+ ProgressCallback,
+ AsyncProgressCallback,
+ AuditLogDownloadResult,
+ download_audit_logs,
+ async_download_audit_logs,
+)
+
+__all__ = ["AuditLogsResource", "AsyncAuditLogsResource"]
+
+
+class AuditLogsResource(SyncAPIResource):
+ """Read audit log records for the authenticated organization."""
+
+ @cached_property
+ def export_destinations(self) -> ExportDestinationsResource:
+ """Read audit log records for the authenticated organization."""
+ return ExportDestinationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AuditLogsResourceWithRawResponse:
+ """
+ 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 AuditLogsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AuditLogsResourceWithStreamingResponse:
+ """
+ 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 AuditLogsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ end: Union[str, datetime],
+ start: Union[str, datetime],
+ auth_strategy: str | Omit = omit,
+ exclude_method: SequenceNotStr[str] | Omit = omit,
+ limit: int | Omit = omit,
+ method: str | Omit = omit,
+ page_token: str | Omit = omit,
+ search: str | Omit = omit,
+ search_user_id: SequenceNotStr[str] | Omit = omit,
+ service: str | 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,
+ ) -> SyncPageTokenPagination[AuditLogEntry]:
+ """API for searching audit logs.
+
+ Limited to at most 30 day search, returns up to
+ 100 records per page. Not recommended for bulk export.
+
+ Args:
+ end: Upper bound (exclusive) for the audit record timestamp.
+
+ start: Lower bound (inclusive) for the audit record timestamp.
+
+ auth_strategy: Filter by authentication strategy.
+
+ exclude_method: Filter out results by HTTP method.
+
+ limit: Maximum number of results to return.
+
+ method: Filter by HTTP method.
+
+ page_token: Opaque page token from X-Next-Page-Token for the next page of older records.
+
+ search: Free-text search over path, user ID, email, client IP, and status.
+
+ search_user_id: Additional user IDs to OR into free-text search.
+
+ service: Filter by service name.
+
+ 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(
+ "/audit-logs",
+ page=SyncPageTokenPagination[AuditLogEntry],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "end": end,
+ "start": start,
+ "auth_strategy": auth_strategy,
+ "exclude_method": exclude_method,
+ "limit": limit,
+ "method": method,
+ "page_token": page_token,
+ "search": search,
+ "search_user_id": search_user_id,
+ "service": service,
+ },
+ audit_log_list_params.AuditLogListParams,
+ ),
+ ),
+ model=AuditLogEntry,
+ )
+
+ def export_chunk(
+ self,
+ *,
+ end: Union[str, datetime],
+ start: Union[str, datetime],
+ auth_strategy: str | Omit = omit,
+ cursor: str | Omit = omit,
+ exclude_method: SequenceNotStr[str] | Omit = omit,
+ format: Literal["jsonl", "jsonl.gz"] | Omit = omit,
+ limit: int | Omit = omit,
+ method: str | Omit = omit,
+ search: str | Omit = omit,
+ search_user_id: SequenceNotStr[str] | Omit = omit,
+ service: str | 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,
+ ) -> BinaryAPIResponse:
+ """
+ Download an organization's audit log records for a time range as a file, for
+ archival, compliance, or offline analysis. For interactive browsing, use GET
+ /audit-logs.
+
+ Args:
+ end: Upper bound (exclusive) for the audit record timestamp.
+
+ start: Lower bound (inclusive) for the audit record timestamp.
+
+ auth_strategy: Filter by authentication strategy.
+
+ cursor: Opaque cursor from X-Next-Cursor for the next chunk of older records.
+
+ exclude_method: Filter out results by HTTP method.
+
+ format: Encoding for the returned chunk.
+
+ limit: Maximum number of records to return in this chunk.
+
+ method: Filter by HTTP method.
+
+ search: Free-text search over path, user ID, email, client IP, and status.
+
+ search_user_id: Additional user IDs to OR into free-text search.
+
+ service: Filter by service name.
+
+ 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
+ """
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ return self._get(
+ "/audit-logs/export/chunk",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "end": end,
+ "start": start,
+ "auth_strategy": auth_strategy,
+ "cursor": cursor,
+ "exclude_method": exclude_method,
+ "format": format,
+ "limit": limit,
+ "method": method,
+ "search": search,
+ "search_user_id": search_user_id,
+ "service": service,
+ },
+ audit_log_export_chunk_params.AuditLogExportChunkParams,
+ ),
+ ),
+ cast_to=BinaryAPIResponse,
+ )
+
+ def download(
+ self,
+ *,
+ to: BinaryIO,
+ end: Union[str, datetime],
+ start: Union[str, datetime],
+ auth_strategy: str | Omit = omit,
+ exclude_method: SequenceNotStr[str] | Omit = omit,
+ limit: int | Omit = omit,
+ method: str | Omit = omit,
+ search: str | Omit = omit,
+ search_user_id: SequenceNotStr[str] | Omit = omit,
+ service: str | Omit = omit,
+ on_progress: ProgressCallback | None = None,
+ max_transfer_retries: int = 6,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AuditLogDownloadResult:
+ """Download a complete gzip-compressed JSON Lines audit log export.
+
+ The SDK writes the export to a writable binary destination, verifies
+ every chunk, and retries transient transfer failures. It does not close
+ the destination. If the download fails, the destination may contain a
+ partial export; use a temporary file and atomic rename when the completed
+ export must be published atomically.
+ """
+
+ def fetch_chunk(cursor: str | None) -> ContextManager[StreamedBinaryAPIResponse]:
+ return self.with_streaming_response.export_chunk(
+ end=end,
+ start=start,
+ auth_strategy=auth_strategy,
+ cursor=cursor if cursor is not None else omit,
+ exclude_method=exclude_method,
+ limit=limit,
+ method=method,
+ search=search,
+ search_user_id=search_user_id,
+ service=service,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ )
+
+ return download_audit_logs(
+ fetch_chunk,
+ to,
+ on_progress=on_progress,
+ max_transfer_retries=max_transfer_retries,
+ )
+
+
+class AsyncAuditLogsResource(AsyncAPIResource):
+ """Read audit log records for the authenticated organization."""
+
+ @cached_property
+ def export_destinations(self) -> AsyncExportDestinationsResource:
+ """Read audit log records for the authenticated organization."""
+ return AsyncExportDestinationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncAuditLogsResourceWithRawResponse:
+ """
+ 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 AsyncAuditLogsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAuditLogsResourceWithStreamingResponse:
+ """
+ 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 AsyncAuditLogsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ end: Union[str, datetime],
+ start: Union[str, datetime],
+ auth_strategy: str | Omit = omit,
+ exclude_method: SequenceNotStr[str] | Omit = omit,
+ limit: int | Omit = omit,
+ method: str | Omit = omit,
+ page_token: str | Omit = omit,
+ search: str | Omit = omit,
+ search_user_id: SequenceNotStr[str] | Omit = omit,
+ service: str | 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[AuditLogEntry, AsyncPageTokenPagination[AuditLogEntry]]:
+ """API for searching audit logs.
+
+ Limited to at most 30 day search, returns up to
+ 100 records per page. Not recommended for bulk export.
+
+ Args:
+ end: Upper bound (exclusive) for the audit record timestamp.
+
+ start: Lower bound (inclusive) for the audit record timestamp.
+
+ auth_strategy: Filter by authentication strategy.
+
+ exclude_method: Filter out results by HTTP method.
+
+ limit: Maximum number of results to return.
+
+ method: Filter by HTTP method.
+
+ page_token: Opaque page token from X-Next-Page-Token for the next page of older records.
+
+ search: Free-text search over path, user ID, email, client IP, and status.
+
+ search_user_id: Additional user IDs to OR into free-text search.
+
+ service: Filter by service name.
+
+ 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(
+ "/audit-logs",
+ page=AsyncPageTokenPagination[AuditLogEntry],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "end": end,
+ "start": start,
+ "auth_strategy": auth_strategy,
+ "exclude_method": exclude_method,
+ "limit": limit,
+ "method": method,
+ "page_token": page_token,
+ "search": search,
+ "search_user_id": search_user_id,
+ "service": service,
+ },
+ audit_log_list_params.AuditLogListParams,
+ ),
+ ),
+ model=AuditLogEntry,
+ )
+
+ async def export_chunk(
+ self,
+ *,
+ end: Union[str, datetime],
+ start: Union[str, datetime],
+ auth_strategy: str | Omit = omit,
+ cursor: str | Omit = omit,
+ exclude_method: SequenceNotStr[str] | Omit = omit,
+ format: Literal["jsonl", "jsonl.gz"] | Omit = omit,
+ limit: int | Omit = omit,
+ method: str | Omit = omit,
+ search: str | Omit = omit,
+ search_user_id: SequenceNotStr[str] | Omit = omit,
+ service: str | 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,
+ ) -> AsyncBinaryAPIResponse:
+ """
+ Download an organization's audit log records for a time range as a file, for
+ archival, compliance, or offline analysis. For interactive browsing, use GET
+ /audit-logs.
+
+ Args:
+ end: Upper bound (exclusive) for the audit record timestamp.
+
+ start: Lower bound (inclusive) for the audit record timestamp.
+
+ auth_strategy: Filter by authentication strategy.
+
+ cursor: Opaque cursor from X-Next-Cursor for the next chunk of older records.
+
+ exclude_method: Filter out results by HTTP method.
+
+ format: Encoding for the returned chunk.
+
+ limit: Maximum number of records to return in this chunk.
+
+ method: Filter by HTTP method.
+
+ search: Free-text search over path, user ID, email, client IP, and status.
+
+ search_user_id: Additional user IDs to OR into free-text search.
+
+ service: Filter by service name.
+
+ 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
+ """
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ return await self._get(
+ "/audit-logs/export/chunk",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "end": end,
+ "start": start,
+ "auth_strategy": auth_strategy,
+ "cursor": cursor,
+ "exclude_method": exclude_method,
+ "format": format,
+ "limit": limit,
+ "method": method,
+ "search": search,
+ "search_user_id": search_user_id,
+ "service": service,
+ },
+ audit_log_export_chunk_params.AuditLogExportChunkParams,
+ ),
+ ),
+ cast_to=AsyncBinaryAPIResponse,
+ )
+
+ async def download(
+ self,
+ *,
+ to: BinaryIO,
+ end: Union[str, datetime],
+ start: Union[str, datetime],
+ auth_strategy: str | Omit = omit,
+ exclude_method: SequenceNotStr[str] | Omit = omit,
+ limit: int | Omit = omit,
+ method: str | Omit = omit,
+ search: str | Omit = omit,
+ search_user_id: SequenceNotStr[str] | Omit = omit,
+ service: str | Omit = omit,
+ on_progress: AsyncProgressCallback | None = None,
+ max_transfer_retries: int = 6,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AuditLogDownloadResult:
+ """Download a complete gzip-compressed JSON Lines audit log export.
+
+ The SDK writes the export to a writable binary destination, verifies
+ every chunk, and retries transient transfer failures. It does not close
+ the destination. If the download fails, the destination may contain a
+ partial export; use a temporary file and atomic rename when the completed
+ export must be published atomically.
+ """
+
+ def fetch_chunk(cursor: str | None) -> AsyncContextManager[AsyncStreamedBinaryAPIResponse]:
+ return self.with_streaming_response.export_chunk(
+ end=end,
+ start=start,
+ auth_strategy=auth_strategy,
+ cursor=cursor if cursor is not None else omit,
+ exclude_method=exclude_method,
+ limit=limit,
+ method=method,
+ search=search,
+ search_user_id=search_user_id,
+ service=service,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ )
+
+ return await async_download_audit_logs(
+ fetch_chunk,
+ to,
+ on_progress=on_progress,
+ max_transfer_retries=max_transfer_retries,
+ )
+
+
+class AuditLogsResourceWithRawResponse:
+ def __init__(self, audit_logs: AuditLogsResource) -> None:
+ self._audit_logs = audit_logs
+
+ self.list = to_raw_response_wrapper(
+ audit_logs.list,
+ )
+ self.export_chunk = to_custom_raw_response_wrapper(
+ audit_logs.export_chunk,
+ BinaryAPIResponse,
+ )
+
+ @cached_property
+ def export_destinations(self) -> ExportDestinationsResourceWithRawResponse:
+ """Read audit log records for the authenticated organization."""
+ return ExportDestinationsResourceWithRawResponse(self._audit_logs.export_destinations)
+
+
+class AsyncAuditLogsResourceWithRawResponse:
+ def __init__(self, audit_logs: AsyncAuditLogsResource) -> None:
+ self._audit_logs = audit_logs
+
+ self.list = async_to_raw_response_wrapper(
+ audit_logs.list,
+ )
+ self.export_chunk = async_to_custom_raw_response_wrapper(
+ audit_logs.export_chunk,
+ AsyncBinaryAPIResponse,
+ )
+
+ @cached_property
+ def export_destinations(self) -> AsyncExportDestinationsResourceWithRawResponse:
+ """Read audit log records for the authenticated organization."""
+ return AsyncExportDestinationsResourceWithRawResponse(self._audit_logs.export_destinations)
+
+
+class AuditLogsResourceWithStreamingResponse:
+ def __init__(self, audit_logs: AuditLogsResource) -> None:
+ self._audit_logs = audit_logs
+
+ self.list = to_streamed_response_wrapper(
+ audit_logs.list,
+ )
+ self.export_chunk = to_custom_streamed_response_wrapper(
+ audit_logs.export_chunk,
+ StreamedBinaryAPIResponse,
+ )
+
+ @cached_property
+ def export_destinations(self) -> ExportDestinationsResourceWithStreamingResponse:
+ """Read audit log records for the authenticated organization."""
+ return ExportDestinationsResourceWithStreamingResponse(self._audit_logs.export_destinations)
+
+
+class AsyncAuditLogsResourceWithStreamingResponse:
+ def __init__(self, audit_logs: AsyncAuditLogsResource) -> None:
+ self._audit_logs = audit_logs
+
+ self.list = async_to_streamed_response_wrapper(
+ audit_logs.list,
+ )
+ self.export_chunk = async_to_custom_streamed_response_wrapper(
+ audit_logs.export_chunk,
+ AsyncStreamedBinaryAPIResponse,
+ )
+
+ @cached_property
+ def export_destinations(self) -> AsyncExportDestinationsResourceWithStreamingResponse:
+ """Read audit log records for the authenticated organization."""
+ return AsyncExportDestinationsResourceWithStreamingResponse(self._audit_logs.export_destinations)
diff --git a/src/kernel/resources/audit_logs/export_destinations.py b/src/kernel/resources/audit_logs/export_destinations.py
new file mode 100644
index 00000000..c5d79624
--- /dev/null
+++ b/src/kernel/resources/audit_logs/export_destinations.py
@@ -0,0 +1,691 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import 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.audit_logs import (
+ export_destination_list_params,
+ export_destination_create_params,
+ export_destination_update_params,
+)
+from ...types.audit_logs.audit_log_export_destination import AuditLogExportDestination
+from ...types.audit_logs.audit_log_export_destination_test_result import AuditLogExportDestinationTestResult
+
+__all__ = ["ExportDestinationsResource", "AsyncExportDestinationsResource"]
+
+
+class ExportDestinationsResource(SyncAPIResource):
+ """Read audit log records for the authenticated organization."""
+
+ @cached_property
+ def with_raw_response(self) -> ExportDestinationsResourceWithRawResponse:
+ """
+ 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 ExportDestinationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ExportDestinationsResourceWithStreamingResponse:
+ """
+ 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 ExportDestinationsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ bucket: str,
+ format: Literal["jsonl.gz"],
+ prefix: str,
+ region: str,
+ role_arn: str,
+ type: Literal["s3"],
+ kms_key_id: str | 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,
+ ) -> AuditLogExportDestination:
+ """Create a paused destination.
+
+ Activate it with a status update once the
+ destination test passes. Requires an active Enterprise plan.
+
+ 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
+ """
+ return self._post(
+ "/audit-logs/export/destinations",
+ body=maybe_transform(
+ {
+ "bucket": bucket,
+ "format": format,
+ "prefix": prefix,
+ "region": region,
+ "role_arn": role_arn,
+ "type": type,
+ "kms_key_id": kms_key_id,
+ },
+ export_destination_create_params.ExportDestinationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestination,
+ )
+
+ 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,
+ ) -> AuditLogExportDestination:
+ """
+ Retrieve details for a single audit log export destination by its 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("/audit-logs/export/destinations/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestination,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ bucket: str | Omit = omit,
+ kms_key_id: str | Omit = omit,
+ prefix: str | Omit = omit,
+ region: str | Omit = omit,
+ role_arn: str | Omit = omit,
+ status: Literal["active", "paused"] | 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,
+ ) -> AuditLogExportDestination:
+ """Apply a partial update to a destination.
+
+ Requires an active Enterprise plan.
+ Returns 409 when the destination was changed concurrently, because the merged
+ configuration this request validated is no longer the one that would be stored;
+ retry against fresh state. Pausing prevents new delivery attempts, but an S3
+ upload already in progress may complete after the response.
+
+ Args:
+ kms_key_id: KMS key ID, alias, or ARN. Set to an empty string to remove the configured KMS
+ key; omit or send null 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("/audit-logs/export/destinations/{id}", id=id),
+ body=maybe_transform(
+ {
+ "bucket": bucket,
+ "kms_key_id": kms_key_id,
+ "prefix": prefix,
+ "region": region,
+ "role_arn": role_arn,
+ "status": status,
+ },
+ export_destination_update_params.ExportDestinationUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestination,
+ )
+
+ 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[AuditLogExportDestination]:
+ """
+ List audit log export destinations for the organization with pagination support.
+
+ Args:
+ limit: Limit the number of destinations to return.
+
+ offset: Offset the number of destinations to return.
+
+ 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(
+ "/audit-logs/export/destinations",
+ page=SyncOffsetPagination[AuditLogExportDestination],
+ 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,
+ },
+ export_destination_list_params.ExportDestinationListParams,
+ ),
+ ),
+ model=AuditLogExportDestination,
+ )
+
+ 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 the destination and prevent new delivery attempts.
+
+ An S3 upload
+ already in progress may complete after the response.
+
+ 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("/audit-logs/export/destinations/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def test(
+ 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,
+ ) -> AuditLogExportDestinationTestResult:
+ """
+ Verify the destination is writable by assuming the configured role and uploading
+ a temporary probe object with the same request metadata as a real delivery.
+ Requires an active Enterprise plan.
+
+ 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._post(
+ path_template("/audit-logs/export/destinations/{id}/test", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestinationTestResult,
+ )
+
+
+class AsyncExportDestinationsResource(AsyncAPIResource):
+ """Read audit log records for the authenticated organization."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncExportDestinationsResourceWithRawResponse:
+ """
+ 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 AsyncExportDestinationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncExportDestinationsResourceWithStreamingResponse:
+ """
+ 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 AsyncExportDestinationsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ bucket: str,
+ format: Literal["jsonl.gz"],
+ prefix: str,
+ region: str,
+ role_arn: str,
+ type: Literal["s3"],
+ kms_key_id: str | 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,
+ ) -> AuditLogExportDestination:
+ """Create a paused destination.
+
+ Activate it with a status update once the
+ destination test passes. Requires an active Enterprise plan.
+
+ 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
+ """
+ return await self._post(
+ "/audit-logs/export/destinations",
+ body=await async_maybe_transform(
+ {
+ "bucket": bucket,
+ "format": format,
+ "prefix": prefix,
+ "region": region,
+ "role_arn": role_arn,
+ "type": type,
+ "kms_key_id": kms_key_id,
+ },
+ export_destination_create_params.ExportDestinationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestination,
+ )
+
+ 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,
+ ) -> AuditLogExportDestination:
+ """
+ Retrieve details for a single audit log export destination by its 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("/audit-logs/export/destinations/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestination,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ bucket: str | Omit = omit,
+ kms_key_id: str | Omit = omit,
+ prefix: str | Omit = omit,
+ region: str | Omit = omit,
+ role_arn: str | Omit = omit,
+ status: Literal["active", "paused"] | 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,
+ ) -> AuditLogExportDestination:
+ """Apply a partial update to a destination.
+
+ Requires an active Enterprise plan.
+ Returns 409 when the destination was changed concurrently, because the merged
+ configuration this request validated is no longer the one that would be stored;
+ retry against fresh state. Pausing prevents new delivery attempts, but an S3
+ upload already in progress may complete after the response.
+
+ Args:
+ kms_key_id: KMS key ID, alias, or ARN. Set to an empty string to remove the configured KMS
+ key; omit or send null 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("/audit-logs/export/destinations/{id}", id=id),
+ body=await async_maybe_transform(
+ {
+ "bucket": bucket,
+ "kms_key_id": kms_key_id,
+ "prefix": prefix,
+ "region": region,
+ "role_arn": role_arn,
+ "status": status,
+ },
+ export_destination_update_params.ExportDestinationUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestination,
+ )
+
+ 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[AuditLogExportDestination, AsyncOffsetPagination[AuditLogExportDestination]]:
+ """
+ List audit log export destinations for the organization with pagination support.
+
+ Args:
+ limit: Limit the number of destinations to return.
+
+ offset: Offset the number of destinations to return.
+
+ 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(
+ "/audit-logs/export/destinations",
+ page=AsyncOffsetPagination[AuditLogExportDestination],
+ 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,
+ },
+ export_destination_list_params.ExportDestinationListParams,
+ ),
+ ),
+ model=AuditLogExportDestination,
+ )
+
+ 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 the destination and prevent new delivery attempts.
+
+ An S3 upload
+ already in progress may complete after the response.
+
+ 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("/audit-logs/export/destinations/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def test(
+ 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,
+ ) -> AuditLogExportDestinationTestResult:
+ """
+ Verify the destination is writable by assuming the configured role and uploading
+ a temporary probe object with the same request metadata as a real delivery.
+ Requires an active Enterprise plan.
+
+ 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._post(
+ path_template("/audit-logs/export/destinations/{id}/test", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuditLogExportDestinationTestResult,
+ )
+
+
+class ExportDestinationsResourceWithRawResponse:
+ def __init__(self, export_destinations: ExportDestinationsResource) -> None:
+ self._export_destinations = export_destinations
+
+ self.create = to_raw_response_wrapper(
+ export_destinations.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ export_destinations.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ export_destinations.update,
+ )
+ self.list = to_raw_response_wrapper(
+ export_destinations.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ export_destinations.delete,
+ )
+ self.test = to_raw_response_wrapper(
+ export_destinations.test,
+ )
+
+
+class AsyncExportDestinationsResourceWithRawResponse:
+ def __init__(self, export_destinations: AsyncExportDestinationsResource) -> None:
+ self._export_destinations = export_destinations
+
+ self.create = async_to_raw_response_wrapper(
+ export_destinations.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ export_destinations.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ export_destinations.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ export_destinations.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ export_destinations.delete,
+ )
+ self.test = async_to_raw_response_wrapper(
+ export_destinations.test,
+ )
+
+
+class ExportDestinationsResourceWithStreamingResponse:
+ def __init__(self, export_destinations: ExportDestinationsResource) -> None:
+ self._export_destinations = export_destinations
+
+ self.create = to_streamed_response_wrapper(
+ export_destinations.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ export_destinations.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ export_destinations.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ export_destinations.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ export_destinations.delete,
+ )
+ self.test = to_streamed_response_wrapper(
+ export_destinations.test,
+ )
+
+
+class AsyncExportDestinationsResourceWithStreamingResponse:
+ def __init__(self, export_destinations: AsyncExportDestinationsResource) -> None:
+ self._export_destinations = export_destinations
+
+ self.create = async_to_streamed_response_wrapper(
+ export_destinations.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ export_destinations.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ export_destinations.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ export_destinations.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ export_destinations.delete,
+ )
+ self.test = async_to_streamed_response_wrapper(
+ export_destinations.test,
+ )
diff --git a/src/kernel/resources/auth/__init__.py b/src/kernel/resources/auth/__init__.py
index a863677f..37b41273 100644
--- a/src/kernel/resources/auth/__init__.py
+++ b/src/kernel/resources/auth/__init__.py
@@ -8,6 +8,14 @@
AuthResourceWithStreamingResponse,
AsyncAuthResourceWithStreamingResponse,
)
+from .context import (
+ ContextResource,
+ AsyncContextResource,
+ ContextResourceWithRawResponse,
+ AsyncContextResourceWithRawResponse,
+ ContextResourceWithStreamingResponse,
+ AsyncContextResourceWithStreamingResponse,
+)
from .connections import (
ConnectionsResource,
AsyncConnectionsResource,
@@ -18,6 +26,12 @@
)
__all__ = [
+ "ContextResource",
+ "AsyncContextResource",
+ "ContextResourceWithRawResponse",
+ "AsyncContextResourceWithRawResponse",
+ "ContextResourceWithStreamingResponse",
+ "AsyncContextResourceWithStreamingResponse",
"ConnectionsResource",
"AsyncConnectionsResource",
"ConnectionsResourceWithRawResponse",
diff --git a/src/kernel/resources/auth/auth.py b/src/kernel/resources/auth/auth.py
index f8744950..b301af28 100644
--- a/src/kernel/resources/auth/auth.py
+++ b/src/kernel/resources/auth/auth.py
@@ -2,6 +2,14 @@
from __future__ import annotations
+from .context import (
+ ContextResource,
+ AsyncContextResource,
+ ContextResourceWithRawResponse,
+ AsyncContextResourceWithRawResponse,
+ ContextResourceWithStreamingResponse,
+ AsyncContextResourceWithStreamingResponse,
+)
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from .connections import (
@@ -17,6 +25,11 @@
class AuthResource(SyncAPIResource):
+ @cached_property
+ def context(self) -> ContextResource:
+ """Inspect the identity and authorization context for the current request."""
+ return ContextResource(self._client)
+
@cached_property
def connections(self) -> ConnectionsResource:
"""Create and manage auth connections for automated credential capture and login."""
@@ -43,6 +56,11 @@ def with_streaming_response(self) -> AuthResourceWithStreamingResponse:
class AsyncAuthResource(AsyncAPIResource):
+ @cached_property
+ def context(self) -> AsyncContextResource:
+ """Inspect the identity and authorization context for the current request."""
+ return AsyncContextResource(self._client)
+
@cached_property
def connections(self) -> AsyncConnectionsResource:
"""Create and manage auth connections for automated credential capture and login."""
@@ -72,6 +90,11 @@ class AuthResourceWithRawResponse:
def __init__(self, auth: AuthResource) -> None:
self._auth = auth
+ @cached_property
+ def context(self) -> ContextResourceWithRawResponse:
+ """Inspect the identity and authorization context for the current request."""
+ return ContextResourceWithRawResponse(self._auth.context)
+
@cached_property
def connections(self) -> ConnectionsResourceWithRawResponse:
"""Create and manage auth connections for automated credential capture and login."""
@@ -82,6 +105,11 @@ class AsyncAuthResourceWithRawResponse:
def __init__(self, auth: AsyncAuthResource) -> None:
self._auth = auth
+ @cached_property
+ def context(self) -> AsyncContextResourceWithRawResponse:
+ """Inspect the identity and authorization context for the current request."""
+ return AsyncContextResourceWithRawResponse(self._auth.context)
+
@cached_property
def connections(self) -> AsyncConnectionsResourceWithRawResponse:
"""Create and manage auth connections for automated credential capture and login."""
@@ -92,6 +120,11 @@ class AuthResourceWithStreamingResponse:
def __init__(self, auth: AuthResource) -> None:
self._auth = auth
+ @cached_property
+ def context(self) -> ContextResourceWithStreamingResponse:
+ """Inspect the identity and authorization context for the current request."""
+ return ContextResourceWithStreamingResponse(self._auth.context)
+
@cached_property
def connections(self) -> ConnectionsResourceWithStreamingResponse:
"""Create and manage auth connections for automated credential capture and login."""
@@ -102,6 +135,11 @@ class AsyncAuthResourceWithStreamingResponse:
def __init__(self, auth: AsyncAuthResource) -> None:
self._auth = auth
+ @cached_property
+ def context(self) -> AsyncContextResourceWithStreamingResponse:
+ """Inspect the identity and authorization context for the current request."""
+ return AsyncContextResourceWithStreamingResponse(self._auth.context)
+
@cached_property
def connections(self) -> AsyncConnectionsResourceWithStreamingResponse:
"""Create and manage auth connections for automated credential capture and login."""
diff --git a/src/kernel/resources/auth/connections.py b/src/kernel/resources/auth/connections.py
index 4befc6e9..fae767b7 100644
--- a/src/kernel/resources/auth/connections.py
+++ b/src/kernel/resources/auth/connections.py
@@ -2,7 +2,8 @@
from __future__ import annotations
-from typing import Any, Dict, cast
+from typing import Any, Dict, Optional, cast
+from typing_extensions import Literal
import httpx
@@ -24,12 +25,15 @@
connection_create_params,
connection_submit_params,
connection_update_params,
+ connection_timeline_params,
)
from ..._base_client import AsyncPaginator, make_request_options
from ...types.auth.managed_auth import ManagedAuth
from ...types.auth.login_response import LoginResponse
from ...types.auth.submit_fields_response import SubmitFieldsResponse
from ...types.auth.connection_follow_response import ConnectionFollowResponse
+from ...types.auth.managed_auth_timeline_event import ManagedAuthTimelineEvent
+from ...types.auth.managed_auth_browser_config_param import ManagedAuthBrowserConfigParam
__all__ = ["ConnectionsResource", "AsyncConnectionsResource"]
@@ -62,8 +66,12 @@ def create(
domain: str,
profile_name: str,
allowed_domains: SequenceNotStr[str] | Omit = omit,
+ auto_reauth: bool | Omit = omit,
+ browser: ManagedAuthBrowserConfigParam | Omit = omit,
+ browser_telemetry: Optional[connection_create_params.BrowserTelemetry] | Omit = omit,
credential: connection_create_params.Credential | Omit = omit,
health_check_interval: int | Omit = omit,
+ health_checks: bool | Omit = omit,
login_url: str | Omit = omit,
proxy: connection_create_params.Proxy | Omit = omit,
record_session: bool | Omit = omit,
@@ -87,8 +95,10 @@ def create(
profile_name: Name of the profile to manage authentication for. If the profile does not exist,
it is created automatically.
- allowed_domains: Additional domains valid for this auth flow (besides the primary domain). Useful
- when login pages redirect to different domains.
+ allowed_domains: Additional hostname roots valid for this auth flow, besides the primary domain.
+ Each value allows credential entry on that exact hostname and its subdomains.
+ Leading `www.` and `*.` labels are normalized away. When omitted or empty,
+ credential entry is unrestricted.
The following SSO/OAuth provider domains are automatically allowed by default
and do not need to be specified:
@@ -105,6 +115,21 @@ def create(
- OneLogin: \\**.onelogin.com
- Ping Identity: _.pingone.com, _.pingidentity.com
+ auto_reauth: Whether to permit automatic re-authentication when a scheduled health check
+ detects an expired session. This is an opt-in flag only — it does not check
+ whether re-auth is actually feasible. Even when true, re-auth only runs when the
+ system has what it needs to perform it (for example, saved credentials for the
+ required login fields), and only after a scheduled health check detects an
+ expired session — so this flag has no effect when `health_checks` is false. When
+ false, expired sessions are marked as `NEEDS_AUTH` instead of attempting
+ re-auth. Defaults to true.
+
+ browser: Default browser configuration for login, reauthentication, and health-check
+ sessions.
+
+ browser_telemetry: Deprecated. Use browser.telemetry. Retained during migration for existing
+ clients.
+
credential:
Reference to credentials for the auth connection. Use one of:
@@ -114,14 +139,19 @@ def create(
health_check_interval: Interval in seconds between automatic health checks. When set, the system
periodically verifies the authentication status and triggers re-authentication
- if needed. Maximum is 86400 (24 hours). Default is 3600 (1 hour). The minimum
- depends on your plan: Enterprise: 300 (5 minutes), Startup: 1200 (20 minutes),
- Hobbyist: 3600 (1 hour).
+ if needed. Maximum is 86400 (24 hours). Default is 3600 (1 hour) or your plan
+ minimum, whichever is larger. The minimum depends on your plan: Enterprise: 300
+ (5 minutes), Startup: 1200 (20 minutes), Hobbyist: 3600 (1 hour), Free: 21600 (6
+ hours).
+
+ health_checks: Whether to enable periodic health checks. When false, the system will not
+ automatically verify authentication status, and `auto_reauth` has no effect on
+ the automatic flow (since re-auth is only triggered by a failed scheduled health
+ check). Defaults to true.
login_url: Optional login page URL to skip discovery
- proxy: Proxy selection. Provide either id or name. The proxy must belong to the
- caller's org.
+ proxy: Deprecated. Use browser.proxy. Retained during migration for existing clients.
record_session: Whether to record browser sessions for this connection by default. Useful for
debugging. Can be overridden per-login. Defaults to false.
@@ -144,8 +174,12 @@ def create(
"domain": domain,
"profile_name": profile_name,
"allowed_domains": allowed_domains,
+ "auto_reauth": auto_reauth,
+ "browser": browser,
+ "browser_telemetry": browser_telemetry,
"credential": credential,
"health_check_interval": health_check_interval,
+ "health_checks": health_checks,
"login_url": login_url,
"proxy": proxy,
"record_session": record_session,
@@ -199,8 +233,12 @@ def update(
id: str,
*,
allowed_domains: SequenceNotStr[str] | Omit = omit,
+ auto_reauth: bool | Omit = omit,
+ browser: ManagedAuthBrowserConfigParam | Omit = omit,
+ browser_telemetry: Optional[connection_update_params.BrowserTelemetry] | Omit = omit,
credential: connection_update_params.Credential | Omit = omit,
health_check_interval: int | Omit = omit,
+ health_checks: bool | Omit = omit,
login_url: str | Omit = omit,
proxy: connection_update_params.Proxy | Omit = omit,
record_session: bool | Omit = omit,
@@ -218,7 +256,24 @@ def update(
updated.
Args:
- allowed_domains: Additional domains valid for this auth flow (replaces existing list)
+ allowed_domains: Additional hostname roots valid for this auth flow. Each value allows credential
+ entry on that exact hostname and its subdomains; leading `www.` and `*.` labels
+ are normalized away. An empty list leaves credential entry unrestricted.
+ Replaces the existing list.
+
+ auto_reauth: Whether automatic re-authentication is permitted for this connection. This is an
+ opt-in flag only — it does not check whether re-auth is actually feasible. Even
+ when true, re-auth only runs when the system has what it needs to perform it
+ (for example, saved credentials for the required login fields), and only after a
+ scheduled health check detects an expired session — so this flag has no effect
+ when `health_checks` is false. When false, expired sessions detected by a health
+ check are marked as `NEEDS_AUTH` instead of attempting re-auth.
+
+ browser: Browser configuration updates for future login, reauthentication, and
+ health-check sessions. Omitted properties remain unchanged.
+
+ browser_telemetry: Deprecated. Use browser.telemetry. Retained during migration for existing
+ clients.
credential:
Reference to credentials for the auth connection. Use one of:
@@ -229,10 +284,14 @@ def update(
health_check_interval: Interval in seconds between automatic health checks
+ health_checks: Whether periodic health checks are enabled. When set to false, the system will
+ not automatically verify authentication status, and `auto_reauth` has no effect
+ on the automatic flow (since re-auth is only triggered by a failed scheduled
+ health check).
+
login_url: Login page URL. Set to empty string to clear.
- proxy: Proxy selection. Provide either id or name. The proxy must belong to the
- caller's org.
+ proxy: Deprecated. Use browser.proxy. Retained during migration for existing clients.
record_session: Whether to record browser sessions for this connection by default
@@ -253,8 +312,12 @@ def update(
body=maybe_transform(
{
"allowed_domains": allowed_domains,
+ "auto_reauth": auto_reauth,
+ "browser": browser,
+ "browser_telemetry": browser_telemetry,
"credential": credential,
"health_check_interval": health_check_interval,
+ "health_checks": health_checks,
"login_url": login_url,
"proxy": proxy,
"record_session": record_session,
@@ -275,6 +338,7 @@ def list(
limit: int | Omit = omit,
offset: int | Omit = omit,
profile_name: str | Omit = omit,
+ query: str | 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,
@@ -294,6 +358,8 @@ def list(
profile_name: Filter by profile name
+ query: Search auth connections by ID, domain, or profile name.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -316,6 +382,7 @@ def list(
"limit": limit,
"offset": offset,
"profile_name": profile_name,
+ "query": query,
},
connection_list_params.ConnectionListParams,
),
@@ -406,8 +473,11 @@ def login(
self,
id: str,
*,
+ browser: ManagedAuthBrowserConfigParam | Omit = omit,
+ browser_telemetry: Optional[connection_login_params.BrowserTelemetry] | Omit = omit,
proxy: connection_login_params.Proxy | Omit = omit,
record_session: bool | Omit = omit,
+ skill_mode: Literal["enabled", "disabled"] | 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,
@@ -422,12 +492,21 @@ def login(
credentials are stored.
Args:
- proxy: Proxy selection. Provide either id or name. The proxy must belong to the
- caller's org.
+ browser: Browser configuration override for this login. Omitted properties inherit the
+ connection defaults.
+
+ browser_telemetry: Deprecated. Use browser.telemetry. Retained during migration for existing
+ clients.
+
+ proxy: Deprecated. Use browser.proxy. Retained during migration for existing clients.
record_session: Override the connection's default for recording this login's browser session.
When omitted, the connection's record_session default is used.
+ skill_mode: Controls whether this login reads and writes learned domain skills. Automatic
+ reauths inherit the selected mode until a later accepted login sets enabled or
+ omits this field. Defaults to enabled when omitted.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -442,8 +521,11 @@ def login(
path_template("/auth/connections/{id}/login", id=id),
body=maybe_transform(
{
+ "browser": browser,
+ "browser_telemetry": browser_telemetry,
"proxy": proxy,
"record_session": record_session,
+ "skill_mode": skill_mode,
},
connection_login_params.ConnectionLoginParams,
),
@@ -457,8 +539,11 @@ def submit(
self,
id: str,
*,
+ field_values: Dict[str, str] | Omit = omit,
fields: Dict[str, str] | Omit = omit,
+ interaction_id: str | Omit = omit,
mfa_option_id: str | Omit = omit,
+ selected_choice_id: str | Omit = omit,
sign_in_option_id: str | Omit = omit,
sso_button_selector: str | Omit = omit,
sso_provider: str | Omit = omit,
@@ -475,10 +560,17 @@ def submit(
progress and get results.
Args:
+ field_values: Canonical map of field ID to submitted value.
+
fields: Map of field name to value
+ interaction_id: Opaque interaction ID returned with canonical fields and choices. Required for
+ canonical submissions.
+
mfa_option_id: The MFA method type to select (when mfa_options were returned)
+ selected_choice_id: Canonical choice ID selected by the user.
+
sign_in_option_id: The sign-in option ID to select (when sign_in_options were returned)
sso_button_selector: XPath selector for the SSO button to click (ODA). Use sso_provider instead for
@@ -501,8 +593,11 @@ def submit(
path_template("/auth/connections/{id}/submit", id=id),
body=maybe_transform(
{
+ "field_values": field_values,
"fields": fields,
+ "interaction_id": interaction_id,
"mfa_option_id": mfa_option_id,
+ "selected_choice_id": selected_choice_id,
"sign_in_option_id": sign_in_option_id,
"sso_button_selector": sso_button_selector,
"sso_provider": sso_provider,
@@ -515,6 +610,62 @@ def submit(
cast_to=SubmitFieldsResponse,
)
+ def timeline(
+ self,
+ id: str,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ type: Literal["login", "reauth", "health_check"] | 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[ManagedAuthTimelineEvent]:
+ """
+ Returns a chronological timeline of events for an auth connection — login
+ attempts, automatic re-auth attempts, and health checks. Events are returned
+ newest-first.
+
+ Args:
+ limit: Maximum number of events to return
+
+ offset: Number of events to skip
+
+ type: Filter the timeline to a single event type.
+
+ 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_api_list(
+ path_template("/auth/connections/{id}/timeline", id=id),
+ page=SyncOffsetPagination[ManagedAuthTimelineEvent],
+ 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,
+ "type": type,
+ },
+ connection_timeline_params.ConnectionTimelineParams,
+ ),
+ ),
+ model=ManagedAuthTimelineEvent,
+ )
+
class AsyncConnectionsResource(AsyncAPIResource):
"""Create and manage auth connections for automated credential capture and login."""
@@ -544,8 +695,12 @@ async def create(
domain: str,
profile_name: str,
allowed_domains: SequenceNotStr[str] | Omit = omit,
+ auto_reauth: bool | Omit = omit,
+ browser: ManagedAuthBrowserConfigParam | Omit = omit,
+ browser_telemetry: Optional[connection_create_params.BrowserTelemetry] | Omit = omit,
credential: connection_create_params.Credential | Omit = omit,
health_check_interval: int | Omit = omit,
+ health_checks: bool | Omit = omit,
login_url: str | Omit = omit,
proxy: connection_create_params.Proxy | Omit = omit,
record_session: bool | Omit = omit,
@@ -569,8 +724,10 @@ async def create(
profile_name: Name of the profile to manage authentication for. If the profile does not exist,
it is created automatically.
- allowed_domains: Additional domains valid for this auth flow (besides the primary domain). Useful
- when login pages redirect to different domains.
+ allowed_domains: Additional hostname roots valid for this auth flow, besides the primary domain.
+ Each value allows credential entry on that exact hostname and its subdomains.
+ Leading `www.` and `*.` labels are normalized away. When omitted or empty,
+ credential entry is unrestricted.
The following SSO/OAuth provider domains are automatically allowed by default
and do not need to be specified:
@@ -587,6 +744,21 @@ async def create(
- OneLogin: \\**.onelogin.com
- Ping Identity: _.pingone.com, _.pingidentity.com
+ auto_reauth: Whether to permit automatic re-authentication when a scheduled health check
+ detects an expired session. This is an opt-in flag only — it does not check
+ whether re-auth is actually feasible. Even when true, re-auth only runs when the
+ system has what it needs to perform it (for example, saved credentials for the
+ required login fields), and only after a scheduled health check detects an
+ expired session — so this flag has no effect when `health_checks` is false. When
+ false, expired sessions are marked as `NEEDS_AUTH` instead of attempting
+ re-auth. Defaults to true.
+
+ browser: Default browser configuration for login, reauthentication, and health-check
+ sessions.
+
+ browser_telemetry: Deprecated. Use browser.telemetry. Retained during migration for existing
+ clients.
+
credential:
Reference to credentials for the auth connection. Use one of:
@@ -596,14 +768,19 @@ async def create(
health_check_interval: Interval in seconds between automatic health checks. When set, the system
periodically verifies the authentication status and triggers re-authentication
- if needed. Maximum is 86400 (24 hours). Default is 3600 (1 hour). The minimum
- depends on your plan: Enterprise: 300 (5 minutes), Startup: 1200 (20 minutes),
- Hobbyist: 3600 (1 hour).
+ if needed. Maximum is 86400 (24 hours). Default is 3600 (1 hour) or your plan
+ minimum, whichever is larger. The minimum depends on your plan: Enterprise: 300
+ (5 minutes), Startup: 1200 (20 minutes), Hobbyist: 3600 (1 hour), Free: 21600 (6
+ hours).
+
+ health_checks: Whether to enable periodic health checks. When false, the system will not
+ automatically verify authentication status, and `auto_reauth` has no effect on
+ the automatic flow (since re-auth is only triggered by a failed scheduled health
+ check). Defaults to true.
login_url: Optional login page URL to skip discovery
- proxy: Proxy selection. Provide either id or name. The proxy must belong to the
- caller's org.
+ proxy: Deprecated. Use browser.proxy. Retained during migration for existing clients.
record_session: Whether to record browser sessions for this connection by default. Useful for
debugging. Can be overridden per-login. Defaults to false.
@@ -626,8 +803,12 @@ async def create(
"domain": domain,
"profile_name": profile_name,
"allowed_domains": allowed_domains,
+ "auto_reauth": auto_reauth,
+ "browser": browser,
+ "browser_telemetry": browser_telemetry,
"credential": credential,
"health_check_interval": health_check_interval,
+ "health_checks": health_checks,
"login_url": login_url,
"proxy": proxy,
"record_session": record_session,
@@ -681,8 +862,12 @@ async def update(
id: str,
*,
allowed_domains: SequenceNotStr[str] | Omit = omit,
+ auto_reauth: bool | Omit = omit,
+ browser: ManagedAuthBrowserConfigParam | Omit = omit,
+ browser_telemetry: Optional[connection_update_params.BrowserTelemetry] | Omit = omit,
credential: connection_update_params.Credential | Omit = omit,
health_check_interval: int | Omit = omit,
+ health_checks: bool | Omit = omit,
login_url: str | Omit = omit,
proxy: connection_update_params.Proxy | Omit = omit,
record_session: bool | Omit = omit,
@@ -700,7 +885,24 @@ async def update(
updated.
Args:
- allowed_domains: Additional domains valid for this auth flow (replaces existing list)
+ allowed_domains: Additional hostname roots valid for this auth flow. Each value allows credential
+ entry on that exact hostname and its subdomains; leading `www.` and `*.` labels
+ are normalized away. An empty list leaves credential entry unrestricted.
+ Replaces the existing list.
+
+ auto_reauth: Whether automatic re-authentication is permitted for this connection. This is an
+ opt-in flag only — it does not check whether re-auth is actually feasible. Even
+ when true, re-auth only runs when the system has what it needs to perform it
+ (for example, saved credentials for the required login fields), and only after a
+ scheduled health check detects an expired session — so this flag has no effect
+ when `health_checks` is false. When false, expired sessions detected by a health
+ check are marked as `NEEDS_AUTH` instead of attempting re-auth.
+
+ browser: Browser configuration updates for future login, reauthentication, and
+ health-check sessions. Omitted properties remain unchanged.
+
+ browser_telemetry: Deprecated. Use browser.telemetry. Retained during migration for existing
+ clients.
credential:
Reference to credentials for the auth connection. Use one of:
@@ -711,10 +913,14 @@ async def update(
health_check_interval: Interval in seconds between automatic health checks
+ health_checks: Whether periodic health checks are enabled. When set to false, the system will
+ not automatically verify authentication status, and `auto_reauth` has no effect
+ on the automatic flow (since re-auth is only triggered by a failed scheduled
+ health check).
+
login_url: Login page URL. Set to empty string to clear.
- proxy: Proxy selection. Provide either id or name. The proxy must belong to the
- caller's org.
+ proxy: Deprecated. Use browser.proxy. Retained during migration for existing clients.
record_session: Whether to record browser sessions for this connection by default
@@ -735,8 +941,12 @@ async def update(
body=await async_maybe_transform(
{
"allowed_domains": allowed_domains,
+ "auto_reauth": auto_reauth,
+ "browser": browser,
+ "browser_telemetry": browser_telemetry,
"credential": credential,
"health_check_interval": health_check_interval,
+ "health_checks": health_checks,
"login_url": login_url,
"proxy": proxy,
"record_session": record_session,
@@ -757,6 +967,7 @@ def list(
limit: int | Omit = omit,
offset: int | Omit = omit,
profile_name: str | Omit = omit,
+ query: str | 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,
@@ -776,6 +987,8 @@ def list(
profile_name: Filter by profile name
+ query: Search auth connections by ID, domain, or profile name.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -798,6 +1011,7 @@ def list(
"limit": limit,
"offset": offset,
"profile_name": profile_name,
+ "query": query,
},
connection_list_params.ConnectionListParams,
),
@@ -888,8 +1102,11 @@ async def login(
self,
id: str,
*,
+ browser: ManagedAuthBrowserConfigParam | Omit = omit,
+ browser_telemetry: Optional[connection_login_params.BrowserTelemetry] | Omit = omit,
proxy: connection_login_params.Proxy | Omit = omit,
record_session: bool | Omit = omit,
+ skill_mode: Literal["enabled", "disabled"] | 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,
@@ -904,12 +1121,21 @@ async def login(
credentials are stored.
Args:
- proxy: Proxy selection. Provide either id or name. The proxy must belong to the
- caller's org.
+ browser: Browser configuration override for this login. Omitted properties inherit the
+ connection defaults.
+
+ browser_telemetry: Deprecated. Use browser.telemetry. Retained during migration for existing
+ clients.
+
+ proxy: Deprecated. Use browser.proxy. Retained during migration for existing clients.
record_session: Override the connection's default for recording this login's browser session.
When omitted, the connection's record_session default is used.
+ skill_mode: Controls whether this login reads and writes learned domain skills. Automatic
+ reauths inherit the selected mode until a later accepted login sets enabled or
+ omits this field. Defaults to enabled when omitted.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -924,8 +1150,11 @@ async def login(
path_template("/auth/connections/{id}/login", id=id),
body=await async_maybe_transform(
{
+ "browser": browser,
+ "browser_telemetry": browser_telemetry,
"proxy": proxy,
"record_session": record_session,
+ "skill_mode": skill_mode,
},
connection_login_params.ConnectionLoginParams,
),
@@ -939,8 +1168,11 @@ async def submit(
self,
id: str,
*,
+ field_values: Dict[str, str] | Omit = omit,
fields: Dict[str, str] | Omit = omit,
+ interaction_id: str | Omit = omit,
mfa_option_id: str | Omit = omit,
+ selected_choice_id: str | Omit = omit,
sign_in_option_id: str | Omit = omit,
sso_button_selector: str | Omit = omit,
sso_provider: str | Omit = omit,
@@ -957,10 +1189,17 @@ async def submit(
progress and get results.
Args:
+ field_values: Canonical map of field ID to submitted value.
+
fields: Map of field name to value
+ interaction_id: Opaque interaction ID returned with canonical fields and choices. Required for
+ canonical submissions.
+
mfa_option_id: The MFA method type to select (when mfa_options were returned)
+ selected_choice_id: Canonical choice ID selected by the user.
+
sign_in_option_id: The sign-in option ID to select (when sign_in_options were returned)
sso_button_selector: XPath selector for the SSO button to click (ODA). Use sso_provider instead for
@@ -983,8 +1222,11 @@ async def submit(
path_template("/auth/connections/{id}/submit", id=id),
body=await async_maybe_transform(
{
+ "field_values": field_values,
"fields": fields,
+ "interaction_id": interaction_id,
"mfa_option_id": mfa_option_id,
+ "selected_choice_id": selected_choice_id,
"sign_in_option_id": sign_in_option_id,
"sso_button_selector": sso_button_selector,
"sso_provider": sso_provider,
@@ -997,6 +1239,62 @@ async def submit(
cast_to=SubmitFieldsResponse,
)
+ def timeline(
+ self,
+ id: str,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ type: Literal["login", "reauth", "health_check"] | 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[ManagedAuthTimelineEvent, AsyncOffsetPagination[ManagedAuthTimelineEvent]]:
+ """
+ Returns a chronological timeline of events for an auth connection — login
+ attempts, automatic re-auth attempts, and health checks. Events are returned
+ newest-first.
+
+ Args:
+ limit: Maximum number of events to return
+
+ offset: Number of events to skip
+
+ type: Filter the timeline to a single event type.
+
+ 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_api_list(
+ path_template("/auth/connections/{id}/timeline", id=id),
+ page=AsyncOffsetPagination[ManagedAuthTimelineEvent],
+ 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,
+ "type": type,
+ },
+ connection_timeline_params.ConnectionTimelineParams,
+ ),
+ ),
+ model=ManagedAuthTimelineEvent,
+ )
+
class ConnectionsResourceWithRawResponse:
def __init__(self, connections: ConnectionsResource) -> None:
@@ -1026,6 +1324,9 @@ def __init__(self, connections: ConnectionsResource) -> None:
self.submit = to_raw_response_wrapper(
connections.submit,
)
+ self.timeline = to_raw_response_wrapper(
+ connections.timeline,
+ )
class AsyncConnectionsResourceWithRawResponse:
@@ -1056,6 +1357,9 @@ def __init__(self, connections: AsyncConnectionsResource) -> None:
self.submit = async_to_raw_response_wrapper(
connections.submit,
)
+ self.timeline = async_to_raw_response_wrapper(
+ connections.timeline,
+ )
class ConnectionsResourceWithStreamingResponse:
@@ -1086,6 +1390,9 @@ def __init__(self, connections: ConnectionsResource) -> None:
self.submit = to_streamed_response_wrapper(
connections.submit,
)
+ self.timeline = to_streamed_response_wrapper(
+ connections.timeline,
+ )
class AsyncConnectionsResourceWithStreamingResponse:
@@ -1116,3 +1423,6 @@ def __init__(self, connections: AsyncConnectionsResource) -> None:
self.submit = async_to_streamed_response_wrapper(
connections.submit,
)
+ self.timeline = async_to_streamed_response_wrapper(
+ connections.timeline,
+ )
diff --git a/src/kernel/resources/auth/context.py b/src/kernel/resources/auth/context.py
new file mode 100644
index 00000000..a4f66f97
--- /dev/null
+++ b/src/kernel/resources/auth/context.py
@@ -0,0 +1,147 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+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.auth.auth_context import AuthContext
+
+__all__ = ["ContextResource", "AsyncContextResource"]
+
+
+class ContextResource(SyncAPIResource):
+ """Inspect the identity and authorization context for the current request."""
+
+ @cached_property
+ def with_raw_response(self) -> ContextResourceWithRawResponse:
+ """
+ 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 ContextResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ContextResourceWithStreamingResponse:
+ """
+ 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 ContextResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AuthContext:
+ """
+ Returns the authenticated principal, organization, credential scope, and
+ effective request scope. The response is derived from the verified request
+ context and does not expose credential secrets.
+ """
+ return self._get(
+ "/auth/context",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuthContext,
+ )
+
+
+class AsyncContextResource(AsyncAPIResource):
+ """Inspect the identity and authorization context for the current request."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncContextResourceWithRawResponse:
+ """
+ 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 AsyncContextResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncContextResourceWithStreamingResponse:
+ """
+ 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 AsyncContextResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AuthContext:
+ """
+ Returns the authenticated principal, organization, credential scope, and
+ effective request scope. The response is derived from the verified request
+ context and does not expose credential secrets.
+ """
+ return await self._get(
+ "/auth/context",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AuthContext,
+ )
+
+
+class ContextResourceWithRawResponse:
+ def __init__(self, context: ContextResource) -> None:
+ self._context = context
+
+ self.retrieve = to_raw_response_wrapper(
+ context.retrieve,
+ )
+
+
+class AsyncContextResourceWithRawResponse:
+ def __init__(self, context: AsyncContextResource) -> None:
+ self._context = context
+
+ self.retrieve = async_to_raw_response_wrapper(
+ context.retrieve,
+ )
+
+
+class ContextResourceWithStreamingResponse:
+ def __init__(self, context: ContextResource) -> None:
+ self._context = context
+
+ self.retrieve = to_streamed_response_wrapper(
+ context.retrieve,
+ )
+
+
+class AsyncContextResourceWithStreamingResponse:
+ def __init__(self, context: AsyncContextResource) -> None:
+ self._context = context
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ context.retrieve,
+ )
diff --git a/src/kernel/resources/browser_pools.py b/src/kernel/resources/browser_pools.py
index 10b23923..61ad0977 100644
--- a/src/kernel/resources/browser_pools.py
+++ b/src/kernel/resources/browser_pools.py
@@ -2,11 +2,14 @@
from __future__ import annotations
-from typing import Dict, Iterable
+from typing import Dict, Iterable, Optional
+from typing_extensions import Literal
import httpx
from ..types import (
+ BrowserMemoryRequest,
+ browser_pool_list_params,
browser_pool_create_params,
browser_pool_delete_params,
browser_pool_update_params,
@@ -23,9 +26,12 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from .._base_client import make_request_options
+from ..pagination import SyncOffsetPagination, AsyncOffsetPagination
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.tags_param import TagsParam
from ..types.browser_pool import BrowserPool
-from ..types.browser_pool_list_response import BrowserPoolListResponse
+from ..types.browser_memory_request import BrowserMemoryRequest
+from ..types.browser_network_config_param import BrowserNetworkConfigParam
from ..types.browser_pool_acquire_response import BrowserPoolAcquireResponse
from ..types.shared_params.browser_profile import BrowserProfile
from ..types.shared_params.browser_viewport import BrowserViewport
@@ -65,11 +71,16 @@ def create(
fill_rate_per_minute: int | Omit = omit,
headless: bool | Omit = omit,
kiosk_mode: bool | Omit = omit,
+ memory: BrowserMemoryRequest | Omit = omit,
name: str | Omit = omit,
- profile: BrowserProfile | Omit = omit,
+ network: BrowserNetworkConfigParam | Omit = omit,
+ profile: browser_pool_create_params.Profile | Omit = omit,
proxy_id: str | Omit = omit,
+ refresh_on_profile_update: bool | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
+ telemetry: Optional[browser_pool_create_params.Telemetry] | Omit = omit,
timeout_seconds: int | Omit = omit,
viewport: BrowserViewport | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -79,8 +90,12 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrowserPool:
- """
- Create a new browser pool with the specified configuration and size.
+ """Create a new browser pool with the specified configuration and size.
+
+ Pooled
+ browsers load their profile read-only: any save_changes on the profile is
+ ignored (not rejected), so pooled browsers never persist changes back to the
+ profile.
Args:
size: Number of browsers to maintain in the pool. The maximum size is determined by
@@ -90,50 +105,77 @@ def create(
chrome_policy: Custom Chrome enterprise policy overrides applied to all browsers in this pool.
Keys are Chrome enterprise policy names; values must match their expected types.
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
- https://chromeenterprise.google/policies/
+ https://chromeenterprise.google/policies/ The serialized JSON payload is capped
+ at 5 MiB.
extensions: List of browser extensions to load into the session. Provide each by id or name.
- fill_rate_per_minute: Percentage of the pool to fill per minute. Defaults to 10%.
+ fill_rate_per_minute: Percentage of the pool to fill per minute. Defaults to 25. The cap is 25 for
+ most organizations but can be raised per-organization, so only the lower bound
+ is enforced here.
headless: If true, launches the browser using a headless image. Defaults to false.
kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
- view.
+ view. Defaults to false.
+
+ memory: Memory for browsers in this pool. Headless: 1GiB (default), 2GiB, or 4GiB.
+ Headful: 8GiB (default) or 16GiB.
name: Optional name for the browser pool. Must be unique within the project.
- profile: Profile selection for the browser session. Provide either id or name. If
- specified, the matching profile will be loaded into the browser session.
- Profiles must be created beforehand.
+ network: Network configuration applied to browsers in this pool. Egress allowlists and
+ proxy routes require proxy-v3; the pool will not provision browsers through
+ Envoy if proxy-v3 is unavailable.
+
+ profile: Profile configuration for browsers in a pool. Provide either id or name.
+ Profiles must be created beforehand. Unlike single browser sessions, pools load
+ the profile read-only and never persist changes back to it, so save_changes is
+ omitted here. Any save_changes value sent on a pool profile is silently ignored
+ rather than rejected.
+
+ proxy_id: Optional proxy to associate to the browser session. Must reference a proxy in
+ the same project as the browser session.
- proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
- belonging to the caller's org.
+ refresh_on_profile_update: When true, flush idle browsers when the profile the pool uses is updated, so
+ pool browsers pick up the latest profile data. When a profile is provided during
+ creation, this defaults to true. Requires a profile to be set on the pool.
+
+ region: Geographic region for the browser pool. It is fixed once the pool is created.
+ Region selection requires a Start-Up or Enterprise plan, defaults to us-east
+ when omitted on create.
start_url: Optional URL to navigate to when a new browser is warmed into the pool.
Best-effort: failures to navigate do not fail pool fill. Only applied to
- newly-warmed browsers — browsers reused via release/acquire keep whatever URL
- the previous lease left them on. Accepts any URL Chromium can resolve, including
+ newly-warmed browsers; browsers reused via release/acquire keep whatever URL the
+ previous lease left them on. Accepts any URL Chromium can resolve, including
chrome:// pages.
stealth: If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
+ mechanisms. Defaults to false.
+
+ telemetry: Telemetry configuration applied to browsers warmed into this pool. Set enabled
+ to true to start capture using the default set, or provide browser category
+ settings. If omitted, null, set to an empty object ({}), set to enabled: false
+ without browser category settings, or all four CDP categories are explicitly
+ disabled, no telemetry is configured on the pool. Only applied to newly-warmed
+ browsers.
timeout_seconds: Default idle timeout in seconds for browsers acquired from this pool before they
- are destroyed. Defaults to 600 seconds if not specified
+ are destroyed. Defaults to 600 seconds. Minimum 10, maximum 259200 (72 hours).
viewport: Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
extra_headers: Send extra headers
@@ -153,11 +195,16 @@ def create(
"fill_rate_per_minute": fill_rate_per_minute,
"headless": headless,
"kiosk_mode": kiosk_mode,
+ "memory": memory,
"name": name,
+ "network": network,
"profile": profile,
"proxy_id": proxy_id,
+ "refresh_on_profile_update": refresh_on_profile_update,
+ "region": region,
"start_url": start_url,
"stealth": stealth,
+ "telemetry": telemetry,
"timeout_seconds": timeout_seconds,
"viewport": viewport,
},
@@ -206,18 +253,23 @@ def update(
self,
id_or_name: str,
*,
- size: int,
chrome_policy: Dict[str, object] | Omit = omit,
discard_all_idle: bool | Omit = omit,
+ expected_network: BrowserNetworkConfigParam | Omit = omit,
extensions: Iterable[BrowserExtension] | Omit = omit,
fill_rate_per_minute: int | Omit = omit,
headless: bool | Omit = omit,
kiosk_mode: bool | Omit = omit,
+ memory: Literal["1GiB", "2GiB", "4GiB", "8GiB", "16GiB"] | Omit = omit,
name: str | Omit = omit,
- profile: BrowserProfile | Omit = omit,
+ network: BrowserNetworkConfigParam | Omit = omit,
+ profile: browser_pool_update_params.Profile | Omit = omit,
proxy_id: str | Omit = omit,
+ refresh_on_profile_update: bool | Omit = omit,
+ size: int | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
+ telemetry: Optional[browser_pool_update_params.Telemetry] | Omit = omit,
timeout_seconds: int | Omit = omit,
viewport: BrowserViewport | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -227,64 +279,116 @@ def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrowserPool:
- """
- Updates the configuration used to create browsers in the pool.
+ """Updates the configuration used to create browsers in the pool.
- Args:
- size: Number of browsers to maintain in the pool. The maximum size is determined by
- your organization's pooled sessions limit (the sum of all pool sizes cannot
- exceed your limit).
+ As with creation,
+ save_changes on the pool profile is ignored (not rejected); pooled browsers
+ never persist changes back to the profile. To clear the profile reference, send
+ `profile: { "id": "" }`. Clearing the profile also disables
+ `refresh_on_profile_update`.
- chrome_policy: Custom Chrome enterprise policy overrides applied to all browsers in this pool.
- Keys are Chrome enterprise policy names; values must match their expected types.
+ Args:
+ chrome_policy: If provided, replaces the custom Chrome enterprise policy overrides applied to
+ all browsers in this pool. Empty object clears any previously-set policy. Keys
+ are Chrome enterprise policy names; values must match their expected types.
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
- https://chromeenterprise.google/policies/
-
- discard_all_idle: Whether to discard all idle browsers and rebuild the pool immediately. Defaults
- to false.
-
- extensions: List of browser extensions to load into the session. Provide each by id or name.
-
- fill_rate_per_minute: Percentage of the pool to fill per minute. Defaults to 10%.
-
- headless: If true, launches the browser using a headless image. Defaults to false.
-
- kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
- view.
-
- name: Optional name for the browser pool. Must be unique within the project.
-
- profile: Profile selection for the browser session. Provide either id or name. If
- specified, the matching profile will be loaded into the browser session.
- Profiles must be created beforehand.
-
- proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
- belonging to the caller's org.
-
- start_url: Optional URL to navigate to when a new browser is warmed into the pool.
- Best-effort: failures to navigate do not fail pool fill. Only applied to
- newly-warmed browsers — browsers reused via release/acquire keep whatever URL
- the previous lease left them on. Accepts any URL Chromium can resolve, including
- chrome:// pages.
-
- stealth: If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
-
- timeout_seconds: Default idle timeout in seconds for browsers acquired from this pool before they
- are destroyed. Defaults to 600 seconds if not specified
+ https://chromeenterprise.google/policies/ The serialized JSON payload is capped
+ at 5 MiB.
+
+ discard_all_idle: Whether to discard all idle browsers and rebuild them immediately with the new
+ configuration. Defaults to false. Only browsers that are idle when the update
+ runs are rebuilt. A browser that is in use during the update keeps its original
+ configuration, except that its allowlist is reset to the current pool baseline
+ on release or the browser is replaced. For other configuration fields, if it is
+ later released with `reuse: true` it returns to the pool with that stale
+ configuration until it is discarded (by this flag on a later update, or by
+ flushing the pool).
+
+ expected_network: Optional optimistic concurrency precondition. Send the network configuration
+ from the last pool response, or {} when it has no network configuration. The
+ entire update is rejected with 409 stale_network if the saved network differs,
+ checked atomically with the write. Omit for unconditional updates. Explicit
+ empty lists differ from omitted lists; route targets must match the saved
+ references (resolved IDs, not equivalent proxy names).
+
+ extensions: If provided, replaces the extension list. Empty array clears all
+ previously-selected extensions. Omit this field to leave extensions unchanged.
+
+ fill_rate_per_minute: If provided, replaces the percentage of the pool to fill per minute. The cap is
+ 25 for most organizations but can be raised per-organization, so only the lower
+ bound is enforced here.
+
+ headless: If provided, replaces whether browsers launch using a headless image.
+
+ kiosk_mode: If provided, replaces whether browsers launch in kiosk mode.
+
+ memory: Memory requested for newly-warmed browsers in this pool: 1GiB, 2GiB, or 4GiB for
+ headless, 8GiB or 16GiB for headful. Existing browsers retain their original
+ allocation. Use discard_all_idle to replace idle browsers.
+
+ name: If provided, replaces the pool name. Empty string is a no-op; the pool name
+ cannot be cleared or reset to empty once assigned.
+
+ network: If provided, replaces the pool's network configuration. Omit to leave the
+ existing configuration unchanged; an empty object ({}) removes it, while
+ network: {private_hosts: []} or network: {proxy_routes: []} sets an explicit
+ empty list; allowed_hosts must be non-empty when supplied. Egress allowlists and
+ proxy routes require proxy-v3; the pool will not provision browsers through
+ Envoy if proxy-v3 is unavailable. Changing allowed_hosts automatically replaces
+ idle browsers. Leased browsers retain their allowlist until release, when it is
+ reset to the current pool baseline or the browser is replaced if it cannot
+ safely receive that baseline. Stale browsers cannot be acquired. Other network
+ fields only apply to browsers created after the update; existing browsers keep
+ those fields until discarded (see discard_all_idle).
+
+ profile: Profile configuration for browsers in a pool. Provide either id or name.
+ Profiles must be created beforehand. Unlike single browser sessions, pools load
+ the profile read-only and never persist changes back to it, so save_changes is
+ omitted here. Any save_changes value sent on a pool profile is silently ignored
+ rather than rejected.
+
+ proxy_id: Empty string clears the previously-selected proxy. Omit this field to leave the
+ proxy unchanged.
+
+ refresh_on_profile_update: If provided, replaces whether idle browsers are flushed when the profile the
+ pool uses is updated. When the pool's profile reference is changed (including
+ newly attached) and this field is omitted, it defaults to true. Re-sending the
+ same profile reference leaves this setting unchanged. Clearing the profile also
+ disables this setting. Requires a profile to be set on the pool.
+
+ size: If provided, replaces the number of browsers to maintain in the pool. The
+ maximum size is determined by your organization's pooled sessions limit (the sum
+ of all pool sizes cannot exceed your limit).
+
+ start_url: If provided, replaces the URL to navigate to when a new browser is warmed into
+ the pool. Empty string clears the previously-set URL. Omit this field to leave
+ it unchanged.
+
+ stealth: If provided, replaces whether browsers launch in stealth mode.
+
+ telemetry: If provided, updates the pool's telemetry configuration. Omit, set to null, or
+ set to an empty object ({}) to leave the existing configuration unchanged. Set
+ enabled to true to enable capture using the default set. Set enabled to false to
+ clear the pool's telemetry. Provide browser category settings for per-category
+ updates, merged onto the pool's current configuration. Only applied to browsers
+ warmed after the update; browsers already in the pool keep their configuration
+ until discarded.
+
+ timeout_seconds: If provided, replaces the default idle timeout in seconds for browsers acquired
+ from this pool before they are destroyed. Minimum 10, maximum 259200 (72 hours).
viewport: Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
extra_headers: Send extra headers
@@ -300,18 +404,23 @@ def update(
path_template("/browser_pools/{id_or_name}", id_or_name=id_or_name),
body=maybe_transform(
{
- "size": size,
"chrome_policy": chrome_policy,
"discard_all_idle": discard_all_idle,
+ "expected_network": expected_network,
"extensions": extensions,
"fill_rate_per_minute": fill_rate_per_minute,
"headless": headless,
"kiosk_mode": kiosk_mode,
+ "memory": memory,
"name": name,
+ "network": network,
"profile": profile,
"proxy_id": proxy_id,
+ "refresh_on_profile_update": refresh_on_profile_update,
+ "size": size,
"start_url": start_url,
"stealth": stealth,
+ "telemetry": telemetry,
"timeout_seconds": timeout_seconds,
"viewport": viewport,
},
@@ -326,20 +435,64 @@ def update(
def list(
self,
*,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | 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,
- ) -> BrowserPoolListResponse:
- """List browser pools owned by the caller's organization."""
- return self._get(
+ ) -> SyncOffsetPagination[BrowserPool]:
+ """
+ List browser pools in the resolved project.
+
+ Args:
+ limit: Limit the number of browser pools to return.
+
+ name: Exact-match filter on browser pool name using the database collation. In
+ production, matching is case- and accent-insensitive. During the default-project
+ migration, unscoped requests prefer a concrete default-project browser pool over
+ a legacy unscoped browser pool with the same name.
+
+ offset: Offset the number of browser pools to return.
+
+ query: Case-insensitive substring match against browser pool name. IDs match by exact
+ value.
+
+ region: Filter pools by geographic region. Omit to list pools in all regions.
+
+ 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(
"/browser_pools",
+ page=SyncOffsetPagination[BrowserPool],
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ "region": region,
+ },
+ browser_pool_list_params.BrowserPoolListParams,
+ ),
),
- cast_to=BrowserPoolListResponse,
+ model=BrowserPool,
)
def delete(
@@ -388,13 +541,18 @@ def acquire(
id_or_name: str,
*,
acquire_timeout_seconds: int | Omit = omit,
+ name: str | Omit = omit,
+ profile: BrowserProfile | Omit = omit,
+ start_url: str | Omit = omit,
+ tags: TagsParam | Omit = omit,
+ telemetry: Optional[browser_pool_acquire_params.Telemetry] | 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,
- ) -> BrowserPoolAcquireResponse:
+ ) -> Optional[BrowserPoolAcquireResponse]:
"""Long-polling endpoint to acquire a browser from the pool.
Returns immediately
@@ -407,6 +565,32 @@ def acquire(
calculated time it would take to fill the pool at the currently configured fill
rate.
+ name: Optional human-readable name for the acquired browser session, used to find it
+ later in the dashboard. Must be unique among active sessions within the pool's
+ project. Applies to this lease only and is cleared when the browser is released
+ back to the pool.
+
+ profile: Profile selection for the browser session. Provide either id or name. If
+ specified, the matching profile will be loaded into the browser session.
+ Profiles must be created beforehand.
+
+ start_url: Optional URL to navigate the acquired browser to. Overrides the pool's start_url
+ for this acquire only. Best-effort: failures to navigate do not fail the
+ acquire.
+
+ tags: Optional user-defined key-value tags for the acquired browser session, used to
+ find and group sessions later. Applies to this lease only and are cleared when
+ the browser is released back to the pool. Up to 50 pairs.
+
+ telemetry: Telemetry override for the acquired browser, applied to this lease only. Merges
+ onto the browser's current (pool-inherited) telemetry using the same
+ per-category semantics as PATCH /browsers: provided categories override the
+ current configuration, omitted categories are inherited. Set enabled to true to
+ resolve the config fresh from the default set, or enabled to false to stop
+ capture. When the browser is released back to the pool with reuse, its telemetry
+ is reset to the pool's baseline, so the override does not carry over to the next
+ lease.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -420,7 +604,14 @@ def acquire(
return self._post(
path_template("/browser_pools/{id_or_name}/acquire", id_or_name=id_or_name),
body=maybe_transform(
- {"acquire_timeout_seconds": acquire_timeout_seconds},
+ {
+ "acquire_timeout_seconds": acquire_timeout_seconds,
+ "name": name,
+ "profile": profile,
+ "start_url": start_url,
+ "tags": tags,
+ "telemetry": telemetry,
+ },
browser_pool_acquire_params.BrowserPoolAcquireParams,
),
options=make_request_options(
@@ -483,7 +674,11 @@ def release(
session_id: Browser session ID to release back to the pool
reuse: Whether to reuse the browser instance or destroy it and create a new one.
- Defaults to true.
+ Defaults to true. A reused browser keeps the configuration it was created with,
+ so it does not pick up pool configuration changes made while it was in use.
+ Release with `reuse: false`, or flush the pool afterward, to rebuild it with the
+ current configuration. Browsers loaded with an acquire-time profile are always
+ destroyed and replaced, even when reuse is true.
extra_headers: Send extra headers
@@ -543,11 +738,16 @@ async def create(
fill_rate_per_minute: int | Omit = omit,
headless: bool | Omit = omit,
kiosk_mode: bool | Omit = omit,
+ memory: BrowserMemoryRequest | Omit = omit,
name: str | Omit = omit,
- profile: BrowserProfile | Omit = omit,
+ network: BrowserNetworkConfigParam | Omit = omit,
+ profile: browser_pool_create_params.Profile | Omit = omit,
proxy_id: str | Omit = omit,
+ refresh_on_profile_update: bool | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
+ telemetry: Optional[browser_pool_create_params.Telemetry] | Omit = omit,
timeout_seconds: int | Omit = omit,
viewport: BrowserViewport | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -557,8 +757,12 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrowserPool:
- """
- Create a new browser pool with the specified configuration and size.
+ """Create a new browser pool with the specified configuration and size.
+
+ Pooled
+ browsers load their profile read-only: any save_changes on the profile is
+ ignored (not rejected), so pooled browsers never persist changes back to the
+ profile.
Args:
size: Number of browsers to maintain in the pool. The maximum size is determined by
@@ -568,50 +772,77 @@ async def create(
chrome_policy: Custom Chrome enterprise policy overrides applied to all browsers in this pool.
Keys are Chrome enterprise policy names; values must match their expected types.
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
- https://chromeenterprise.google/policies/
+ https://chromeenterprise.google/policies/ The serialized JSON payload is capped
+ at 5 MiB.
extensions: List of browser extensions to load into the session. Provide each by id or name.
- fill_rate_per_minute: Percentage of the pool to fill per minute. Defaults to 10%.
+ fill_rate_per_minute: Percentage of the pool to fill per minute. Defaults to 25. The cap is 25 for
+ most organizations but can be raised per-organization, so only the lower bound
+ is enforced here.
headless: If true, launches the browser using a headless image. Defaults to false.
kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
- view.
+ view. Defaults to false.
+
+ memory: Memory for browsers in this pool. Headless: 1GiB (default), 2GiB, or 4GiB.
+ Headful: 8GiB (default) or 16GiB.
name: Optional name for the browser pool. Must be unique within the project.
- profile: Profile selection for the browser session. Provide either id or name. If
- specified, the matching profile will be loaded into the browser session.
- Profiles must be created beforehand.
+ network: Network configuration applied to browsers in this pool. Egress allowlists and
+ proxy routes require proxy-v3; the pool will not provision browsers through
+ Envoy if proxy-v3 is unavailable.
- proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
- belonging to the caller's org.
+ profile: Profile configuration for browsers in a pool. Provide either id or name.
+ Profiles must be created beforehand. Unlike single browser sessions, pools load
+ the profile read-only and never persist changes back to it, so save_changes is
+ omitted here. Any save_changes value sent on a pool profile is silently ignored
+ rather than rejected.
+
+ proxy_id: Optional proxy to associate to the browser session. Must reference a proxy in
+ the same project as the browser session.
+
+ refresh_on_profile_update: When true, flush idle browsers when the profile the pool uses is updated, so
+ pool browsers pick up the latest profile data. When a profile is provided during
+ creation, this defaults to true. Requires a profile to be set on the pool.
+
+ region: Geographic region for the browser pool. It is fixed once the pool is created.
+ Region selection requires a Start-Up or Enterprise plan, defaults to us-east
+ when omitted on create.
start_url: Optional URL to navigate to when a new browser is warmed into the pool.
Best-effort: failures to navigate do not fail pool fill. Only applied to
- newly-warmed browsers — browsers reused via release/acquire keep whatever URL
- the previous lease left them on. Accepts any URL Chromium can resolve, including
+ newly-warmed browsers; browsers reused via release/acquire keep whatever URL the
+ previous lease left them on. Accepts any URL Chromium can resolve, including
chrome:// pages.
stealth: If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
+ mechanisms. Defaults to false.
+
+ telemetry: Telemetry configuration applied to browsers warmed into this pool. Set enabled
+ to true to start capture using the default set, or provide browser category
+ settings. If omitted, null, set to an empty object ({}), set to enabled: false
+ without browser category settings, or all four CDP categories are explicitly
+ disabled, no telemetry is configured on the pool. Only applied to newly-warmed
+ browsers.
timeout_seconds: Default idle timeout in seconds for browsers acquired from this pool before they
- are destroyed. Defaults to 600 seconds if not specified
+ are destroyed. Defaults to 600 seconds. Minimum 10, maximum 259200 (72 hours).
viewport: Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
extra_headers: Send extra headers
@@ -631,11 +862,16 @@ async def create(
"fill_rate_per_minute": fill_rate_per_minute,
"headless": headless,
"kiosk_mode": kiosk_mode,
+ "memory": memory,
"name": name,
+ "network": network,
"profile": profile,
"proxy_id": proxy_id,
+ "refresh_on_profile_update": refresh_on_profile_update,
+ "region": region,
"start_url": start_url,
"stealth": stealth,
+ "telemetry": telemetry,
"timeout_seconds": timeout_seconds,
"viewport": viewport,
},
@@ -684,18 +920,23 @@ async def update(
self,
id_or_name: str,
*,
- size: int,
chrome_policy: Dict[str, object] | Omit = omit,
discard_all_idle: bool | Omit = omit,
+ expected_network: BrowserNetworkConfigParam | Omit = omit,
extensions: Iterable[BrowserExtension] | Omit = omit,
fill_rate_per_minute: int | Omit = omit,
headless: bool | Omit = omit,
kiosk_mode: bool | Omit = omit,
+ memory: Literal["1GiB", "2GiB", "4GiB", "8GiB", "16GiB"] | Omit = omit,
name: str | Omit = omit,
- profile: BrowserProfile | Omit = omit,
+ network: BrowserNetworkConfigParam | Omit = omit,
+ profile: browser_pool_update_params.Profile | Omit = omit,
proxy_id: str | Omit = omit,
+ refresh_on_profile_update: bool | Omit = omit,
+ size: int | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
+ telemetry: Optional[browser_pool_update_params.Telemetry] | Omit = omit,
timeout_seconds: int | Omit = omit,
viewport: BrowserViewport | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -705,64 +946,116 @@ async def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrowserPool:
- """
- Updates the configuration used to create browsers in the pool.
+ """Updates the configuration used to create browsers in the pool.
- Args:
- size: Number of browsers to maintain in the pool. The maximum size is determined by
- your organization's pooled sessions limit (the sum of all pool sizes cannot
- exceed your limit).
+ As with creation,
+ save_changes on the pool profile is ignored (not rejected); pooled browsers
+ never persist changes back to the profile. To clear the profile reference, send
+ `profile: { "id": "" }`. Clearing the profile also disables
+ `refresh_on_profile_update`.
- chrome_policy: Custom Chrome enterprise policy overrides applied to all browsers in this pool.
- Keys are Chrome enterprise policy names; values must match their expected types.
+ Args:
+ chrome_policy: If provided, replaces the custom Chrome enterprise policy overrides applied to
+ all browsers in this pool. Empty object clears any previously-set policy. Keys
+ are Chrome enterprise policy names; values must match their expected types.
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
- https://chromeenterprise.google/policies/
-
- discard_all_idle: Whether to discard all idle browsers and rebuild the pool immediately. Defaults
- to false.
-
- extensions: List of browser extensions to load into the session. Provide each by id or name.
-
- fill_rate_per_minute: Percentage of the pool to fill per minute. Defaults to 10%.
-
- headless: If true, launches the browser using a headless image. Defaults to false.
-
- kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
- view.
-
- name: Optional name for the browser pool. Must be unique within the project.
-
- profile: Profile selection for the browser session. Provide either id or name. If
- specified, the matching profile will be loaded into the browser session.
- Profiles must be created beforehand.
-
- proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
- belonging to the caller's org.
-
- start_url: Optional URL to navigate to when a new browser is warmed into the pool.
- Best-effort: failures to navigate do not fail pool fill. Only applied to
- newly-warmed browsers — browsers reused via release/acquire keep whatever URL
- the previous lease left them on. Accepts any URL Chromium can resolve, including
- chrome:// pages.
-
- stealth: If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
-
- timeout_seconds: Default idle timeout in seconds for browsers acquired from this pool before they
- are destroyed. Defaults to 600 seconds if not specified
+ https://chromeenterprise.google/policies/ The serialized JSON payload is capped
+ at 5 MiB.
+
+ discard_all_idle: Whether to discard all idle browsers and rebuild them immediately with the new
+ configuration. Defaults to false. Only browsers that are idle when the update
+ runs are rebuilt. A browser that is in use during the update keeps its original
+ configuration, except that its allowlist is reset to the current pool baseline
+ on release or the browser is replaced. For other configuration fields, if it is
+ later released with `reuse: true` it returns to the pool with that stale
+ configuration until it is discarded (by this flag on a later update, or by
+ flushing the pool).
+
+ expected_network: Optional optimistic concurrency precondition. Send the network configuration
+ from the last pool response, or {} when it has no network configuration. The
+ entire update is rejected with 409 stale_network if the saved network differs,
+ checked atomically with the write. Omit for unconditional updates. Explicit
+ empty lists differ from omitted lists; route targets must match the saved
+ references (resolved IDs, not equivalent proxy names).
+
+ extensions: If provided, replaces the extension list. Empty array clears all
+ previously-selected extensions. Omit this field to leave extensions unchanged.
+
+ fill_rate_per_minute: If provided, replaces the percentage of the pool to fill per minute. The cap is
+ 25 for most organizations but can be raised per-organization, so only the lower
+ bound is enforced here.
+
+ headless: If provided, replaces whether browsers launch using a headless image.
+
+ kiosk_mode: If provided, replaces whether browsers launch in kiosk mode.
+
+ memory: Memory requested for newly-warmed browsers in this pool: 1GiB, 2GiB, or 4GiB for
+ headless, 8GiB or 16GiB for headful. Existing browsers retain their original
+ allocation. Use discard_all_idle to replace idle browsers.
+
+ name: If provided, replaces the pool name. Empty string is a no-op; the pool name
+ cannot be cleared or reset to empty once assigned.
+
+ network: If provided, replaces the pool's network configuration. Omit to leave the
+ existing configuration unchanged; an empty object ({}) removes it, while
+ network: {private_hosts: []} or network: {proxy_routes: []} sets an explicit
+ empty list; allowed_hosts must be non-empty when supplied. Egress allowlists and
+ proxy routes require proxy-v3; the pool will not provision browsers through
+ Envoy if proxy-v3 is unavailable. Changing allowed_hosts automatically replaces
+ idle browsers. Leased browsers retain their allowlist until release, when it is
+ reset to the current pool baseline or the browser is replaced if it cannot
+ safely receive that baseline. Stale browsers cannot be acquired. Other network
+ fields only apply to browsers created after the update; existing browsers keep
+ those fields until discarded (see discard_all_idle).
+
+ profile: Profile configuration for browsers in a pool. Provide either id or name.
+ Profiles must be created beforehand. Unlike single browser sessions, pools load
+ the profile read-only and never persist changes back to it, so save_changes is
+ omitted here. Any save_changes value sent on a pool profile is silently ignored
+ rather than rejected.
+
+ proxy_id: Empty string clears the previously-selected proxy. Omit this field to leave the
+ proxy unchanged.
+
+ refresh_on_profile_update: If provided, replaces whether idle browsers are flushed when the profile the
+ pool uses is updated. When the pool's profile reference is changed (including
+ newly attached) and this field is omitted, it defaults to true. Re-sending the
+ same profile reference leaves this setting unchanged. Clearing the profile also
+ disables this setting. Requires a profile to be set on the pool.
+
+ size: If provided, replaces the number of browsers to maintain in the pool. The
+ maximum size is determined by your organization's pooled sessions limit (the sum
+ of all pool sizes cannot exceed your limit).
+
+ start_url: If provided, replaces the URL to navigate to when a new browser is warmed into
+ the pool. Empty string clears the previously-set URL. Omit this field to leave
+ it unchanged.
+
+ stealth: If provided, replaces whether browsers launch in stealth mode.
+
+ telemetry: If provided, updates the pool's telemetry configuration. Omit, set to null, or
+ set to an empty object ({}) to leave the existing configuration unchanged. Set
+ enabled to true to enable capture using the default set. Set enabled to false to
+ clear the pool's telemetry. Provide browser category settings for per-category
+ updates, merged onto the pool's current configuration. Only applied to browsers
+ warmed after the update; browsers already in the pool keep their configuration
+ until discarded.
+
+ timeout_seconds: If provided, replaces the default idle timeout in seconds for browsers acquired
+ from this pool before they are destroyed. Minimum 10, maximum 259200 (72 hours).
viewport: Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
extra_headers: Send extra headers
@@ -778,18 +1071,23 @@ async def update(
path_template("/browser_pools/{id_or_name}", id_or_name=id_or_name),
body=await async_maybe_transform(
{
- "size": size,
"chrome_policy": chrome_policy,
"discard_all_idle": discard_all_idle,
+ "expected_network": expected_network,
"extensions": extensions,
"fill_rate_per_minute": fill_rate_per_minute,
"headless": headless,
"kiosk_mode": kiosk_mode,
+ "memory": memory,
"name": name,
+ "network": network,
"profile": profile,
"proxy_id": proxy_id,
+ "refresh_on_profile_update": refresh_on_profile_update,
+ "size": size,
"start_url": start_url,
"stealth": stealth,
+ "telemetry": telemetry,
"timeout_seconds": timeout_seconds,
"viewport": viewport,
},
@@ -801,23 +1099,67 @@ async def update(
cast_to=BrowserPool,
)
- async def list(
+ def list(
self,
*,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | 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,
- ) -> BrowserPoolListResponse:
- """List browser pools owned by the caller's organization."""
- return await self._get(
+ ) -> AsyncPaginator[BrowserPool, AsyncOffsetPagination[BrowserPool]]:
+ """
+ List browser pools in the resolved project.
+
+ Args:
+ limit: Limit the number of browser pools to return.
+
+ name: Exact-match filter on browser pool name using the database collation. In
+ production, matching is case- and accent-insensitive. During the default-project
+ migration, unscoped requests prefer a concrete default-project browser pool over
+ a legacy unscoped browser pool with the same name.
+
+ offset: Offset the number of browser pools to return.
+
+ query: Case-insensitive substring match against browser pool name. IDs match by exact
+ value.
+
+ region: Filter pools by geographic region. Omit to list pools in all regions.
+
+ 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(
"/browser_pools",
+ page=AsyncOffsetPagination[BrowserPool],
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ "region": region,
+ },
+ browser_pool_list_params.BrowserPoolListParams,
+ ),
),
- cast_to=BrowserPoolListResponse,
+ model=BrowserPool,
)
async def delete(
@@ -866,13 +1208,18 @@ async def acquire(
id_or_name: str,
*,
acquire_timeout_seconds: int | Omit = omit,
+ name: str | Omit = omit,
+ profile: BrowserProfile | Omit = omit,
+ start_url: str | Omit = omit,
+ tags: TagsParam | Omit = omit,
+ telemetry: Optional[browser_pool_acquire_params.Telemetry] | 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,
- ) -> BrowserPoolAcquireResponse:
+ ) -> Optional[BrowserPoolAcquireResponse]:
"""Long-polling endpoint to acquire a browser from the pool.
Returns immediately
@@ -885,6 +1232,32 @@ async def acquire(
calculated time it would take to fill the pool at the currently configured fill
rate.
+ name: Optional human-readable name for the acquired browser session, used to find it
+ later in the dashboard. Must be unique among active sessions within the pool's
+ project. Applies to this lease only and is cleared when the browser is released
+ back to the pool.
+
+ profile: Profile selection for the browser session. Provide either id or name. If
+ specified, the matching profile will be loaded into the browser session.
+ Profiles must be created beforehand.
+
+ start_url: Optional URL to navigate the acquired browser to. Overrides the pool's start_url
+ for this acquire only. Best-effort: failures to navigate do not fail the
+ acquire.
+
+ tags: Optional user-defined key-value tags for the acquired browser session, used to
+ find and group sessions later. Applies to this lease only and are cleared when
+ the browser is released back to the pool. Up to 50 pairs.
+
+ telemetry: Telemetry override for the acquired browser, applied to this lease only. Merges
+ onto the browser's current (pool-inherited) telemetry using the same
+ per-category semantics as PATCH /browsers: provided categories override the
+ current configuration, omitted categories are inherited. Set enabled to true to
+ resolve the config fresh from the default set, or enabled to false to stop
+ capture. When the browser is released back to the pool with reuse, its telemetry
+ is reset to the pool's baseline, so the override does not carry over to the next
+ lease.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -898,7 +1271,14 @@ async def acquire(
return await self._post(
path_template("/browser_pools/{id_or_name}/acquire", id_or_name=id_or_name),
body=await async_maybe_transform(
- {"acquire_timeout_seconds": acquire_timeout_seconds},
+ {
+ "acquire_timeout_seconds": acquire_timeout_seconds,
+ "name": name,
+ "profile": profile,
+ "start_url": start_url,
+ "tags": tags,
+ "telemetry": telemetry,
+ },
browser_pool_acquire_params.BrowserPoolAcquireParams,
),
options=make_request_options(
@@ -961,7 +1341,11 @@ async def release(
session_id: Browser session ID to release back to the pool
reuse: Whether to reuse the browser instance or destroy it and create a new one.
- Defaults to true.
+ Defaults to true. A reused browser keeps the configuration it was created with,
+ so it does not pick up pool configuration changes made while it was in use.
+ Release with `reuse: false`, or flush the pool afterward, to rebuild it with the
+ current configuration. Browsers loaded with an acquire-time profile are always
+ destroyed and replaced, even when reuse is true.
extra_headers: Send extra headers
diff --git a/src/kernel/resources/browsers/__init__.py b/src/kernel/resources/browsers/__init__.py
index a1acee20..c6ee6a5f 100644
--- a/src/kernel/resources/browsers/__init__.py
+++ b/src/kernel/resources/browsers/__init__.py
@@ -16,6 +16,14 @@
LogsResourceWithStreamingResponse,
AsyncLogsResourceWithStreamingResponse,
)
+from .webmcp import (
+ WebmcpResource,
+ AsyncWebmcpResource,
+ WebmcpResourceWithRawResponse,
+ AsyncWebmcpResourceWithRawResponse,
+ WebmcpResourceWithStreamingResponse,
+ AsyncWebmcpResourceWithStreamingResponse,
+)
from .process import (
ProcessResource,
AsyncProcessResource,
@@ -48,6 +56,14 @@
ComputerResourceWithStreamingResponse,
AsyncComputerResourceWithStreamingResponse,
)
+from .telemetry import (
+ TelemetryResource,
+ AsyncTelemetryResource,
+ TelemetryResourceWithRawResponse,
+ AsyncTelemetryResourceWithRawResponse,
+ TelemetryResourceWithStreamingResponse,
+ AsyncTelemetryResourceWithStreamingResponse,
+)
from .playwright import (
PlaywrightResource,
AsyncPlaywrightResource,
@@ -58,6 +74,12 @@
)
__all__ = [
+ "TelemetryResource",
+ "AsyncTelemetryResource",
+ "TelemetryResourceWithRawResponse",
+ "AsyncTelemetryResourceWithRawResponse",
+ "TelemetryResourceWithStreamingResponse",
+ "AsyncTelemetryResourceWithStreamingResponse",
"ReplaysResource",
"AsyncReplaysResource",
"ReplaysResourceWithRawResponse",
@@ -94,6 +116,12 @@
"AsyncPlaywrightResourceWithRawResponse",
"PlaywrightResourceWithStreamingResponse",
"AsyncPlaywrightResourceWithStreamingResponse",
+ "WebmcpResource",
+ "AsyncWebmcpResource",
+ "WebmcpResourceWithRawResponse",
+ "AsyncWebmcpResourceWithRawResponse",
+ "WebmcpResourceWithStreamingResponse",
+ "AsyncWebmcpResourceWithStreamingResponse",
"BrowsersResource",
"AsyncBrowsersResource",
"BrowsersResourceWithRawResponse",
diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py
index d1dba086..885c7709 100644
--- a/src/kernel/resources/browsers/browsers.py
+++ b/src/kernel/resources/browsers/browsers.py
@@ -2,7 +2,6 @@
from __future__ import annotations
-import typing_extensions
from typing import Dict, Mapping, Iterable, Iterator, Optional, AsyncIterator, cast
from contextlib import contextmanager, asynccontextmanager
from typing_extensions import Literal
@@ -26,10 +25,12 @@
AsyncFsResourceWithStreamingResponse,
)
from ...types import (
+ BrowserVideoMemory,
+ BrowserMemoryRequest,
browser_curl_params,
browser_list_params,
+ browser_repl_params,
browser_create_params,
- browser_delete_params,
browser_update_params,
browser_retrieve_params,
browser_load_extensions_params,
@@ -62,13 +63,13 @@
AsyncComputerResourceWithStreamingResponse,
)
from ..._compat import cached_property
-from .playwright import (
- PlaywrightResource,
- AsyncPlaywrightResource,
- PlaywrightResourceWithRawResponse,
- AsyncPlaywrightResourceWithRawResponse,
- PlaywrightResourceWithStreamingResponse,
- AsyncPlaywrightResourceWithStreamingResponse,
+from .telemetry import (
+ TelemetryResource,
+ AsyncTelemetryResource,
+ TelemetryResourceWithRawResponse,
+ AsyncTelemetryResourceWithRawResponse,
+ TelemetryResourceWithStreamingResponse,
+ AsyncTelemetryResourceWithStreamingResponse,
)
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -78,28 +79,58 @@
async_to_streamed_response_wrapper,
)
from ...pagination import SyncOffsetPagination, AsyncOffsetPagination
+from .webmcp.webmcp import (
+ WebmcpResource,
+ AsyncWebmcpResource,
+ WebmcpResourceWithRawResponse,
+ AsyncWebmcpResourceWithRawResponse,
+ WebmcpResourceWithStreamingResponse,
+ AsyncWebmcpResourceWithStreamingResponse,
+)
from ..._base_client import AsyncPaginator, make_request_options
+from ...types.tags_param import TagsParam
+from .playwright.playwright import (
+ PlaywrightResource,
+ AsyncPlaywrightResource,
+ PlaywrightResourceWithRawResponse,
+ AsyncPlaywrightResourceWithRawResponse,
+ PlaywrightResourceWithStreamingResponse,
+ AsyncPlaywrightResourceWithStreamingResponse,
+)
+from ...types.browser_repl_result import BrowserReplResult
+from ...types.browser_video_memory import BrowserVideoMemory
from ...types.browser_curl_response import BrowserCurlResponse
from ...types.browser_list_response import BrowserListResponse
+from ...types.vault_reference_param import VaultReferenceParam
from ...lib.browser_routing.raw_http import (
stream_via_browser_route,
request_via_browser_route,
async_stream_via_browser_route,
async_request_via_browser_route,
)
+from ...types.browser_memory_request import BrowserMemoryRequest
from ...types.browser_create_response import BrowserCreateResponse
from ...types.browser_update_response import BrowserUpdateResponse
-from ...types.browser_persistence_param import BrowserPersistenceParam
from ...types.browser_retrieve_response import BrowserRetrieveResponse
+from ...types.browser_proxy_config_param import BrowserProxyConfigParam
+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"]
class BrowsersResource(SyncAPIResource):
- """Create and manage browser sessions."""
+ @cached_property
+ def telemetry(self) -> TelemetryResource:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return TelemetryResource(self._client)
@cached_property
def replays(self) -> ReplaysResource:
@@ -123,13 +154,21 @@ def logs(self) -> LogsResource:
@cached_property
def computer(self) -> ComputerResource:
+ """Control mouse, keyboard, and screen on the browser instance."""
return ComputerResource(self._client)
@cached_property
def playwright(self) -> PlaywrightResource:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return PlaywrightResource(self._client)
+ @cached_property
+ def webmcp(self) -> WebmcpResource:
+ """Discover and invoke native page tools across the browser instance."""
+ return WebmcpResource(self._client)
+
@cached_property
def with_raw_response(self) -> BrowsersResourceWithRawResponse:
"""
@@ -152,17 +191,27 @@ def with_streaming_response(self) -> BrowsersResourceWithStreamingResponse:
def create(
self,
*,
+ chrome_policy: Dict[str, object] | Omit = omit,
extensions: Iterable[BrowserExtension] | Omit = omit,
gpu: bool | Omit = omit,
headless: bool | Omit = omit,
invocation_id: str | Omit = omit,
kiosk_mode: bool | Omit = omit,
- persistence: BrowserPersistenceParam | Omit = omit,
+ location: BrowserLocationRequestParam | Omit = omit,
+ memory: BrowserMemoryRequest | Omit = omit,
+ name: str | Omit = omit,
+ network: BrowserNetworkConfigParam | Omit = omit,
profile: BrowserProfile | Omit = omit,
+ proxy: BrowserProxyConfigParam | Omit = omit,
proxy_id: str | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
+ tags: TagsParam | Omit = omit,
+ telemetry: Optional[browser_create_params.Telemetry] | Omit = omit,
timeout_seconds: int | Omit = omit,
+ vaults: Iterable[VaultReferenceParam] | Omit = omit,
+ video_memory: BrowserVideoMemory | Omit = omit,
viewport: BrowserViewport | 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.
@@ -175,10 +224,15 @@ def create(
Create a new browser session from within an action.
Args:
+ chrome_policy: Custom Chrome enterprise policy overrides applied to this browser session. Keys
+ are Chrome enterprise policy names; values must match their expected types.
+ Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
+ https://chromeenterprise.google/policies/
+
extensions: List of browser extensions to load into the session. Provide each by id or name.
gpu: If true, enables GPU acceleration for the browser session. Requires Start-Up or
- Enterprise plan and headless=false.
+ Enterprise plan, headless=false, and region=us-east.
headless: If true, launches the browser using a headless image (no VNC/GUI). Defaults to
false.
@@ -188,23 +242,58 @@ def create(
kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
view.
- persistence: DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.
+ 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.
+
+ name: Optional human-readable name for the browser session, used to find it later in
+ the dashboard. Must be unique among active sessions within the project. Can be
+ changed later via PATCH /browsers/{id_or_name}.
+
+ network: Network configuration for the browser session. Only allowed_hosts can be changed
+ after creation, with PATCH /browsers/{id_or_name}.
profile: Profile selection for the browser session. Provide either id or name. If
specified, the matching profile will be loaded into the browser session.
Profiles must be created beforehand.
- proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
- belonging to the caller's org.
-
- start_url: Optional URL to navigate to immediately after the browser is created.
- Best-effort: failures to navigate do not fail browser creation. Any pre-existing
- tabs are reduced to a single tab which is then navigated. Accepts any URL
- Chromium can resolve, including chrome:// pages. Ignored when reusing an
- existing persistent session.
-
- stealth: If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
+ proxy: Proxy configuration for the browser session. Cannot be combined with proxy_id.
+ Omit to use the browser default: stealth browsers use Kernel's default stealth
+ proxy, while non-stealth browsers use direct egress. Set mode to direct to force
+ direct egress regardless of stealth. Set mode to default to explicitly use the
+ browser default: Kernel's default stealth proxy when stealth=true, or direct
+ egress when stealth=false. Select id or name to use that proxy regardless of
+ stealth. Proxy selection does not change stealth or CAPTCHA solver behavior.
+
+ proxy_id: Optional proxy to associate to the browser session. Must reference a proxy in
+ the same project as the browser session. Deprecated in favor of proxy.
+
+ region: Geographic region for the browser session. It is fixed once the session is
+ created. Region selection requires a Start-Up or Enterprise plan, defaults to
+ us-east when omitted on create.
+
+ start_url: Optional URL to open when the browser session is created. Navigation is
+ best-effort, so navigation failures do not prevent the session from being
+ created.
+
+ stealth: If true, launches the browser in stealth mode and enables the CAPTCHA solver.
+ Defaults to false. When proxy is omitted, stealth browsers use Kernel's default
+ stealth proxy and non-stealth browsers use direct egress. An explicit proxy
+ configuration changes only egress; it does not enable or disable stealth or the
+ CAPTCHA solver.
+
+ tags: Optional user-defined key-value tags for the browser session, used to find and
+ group sessions later. Can be changed later via PATCH /browsers/{id_or_name}. Up
+ to 50 pairs.
+
+ telemetry: Telemetry configuration for the browser session. Set enabled to true to start
+ capture using VM defaults, or provide browser category settings. If omitted,
+ null, set to an empty object ({}), set to enabled: false without browser
+ category settings, or all four categories are explicitly disabled, capture is
+ not started.
timeout_seconds: The number of seconds of inactivity before the browser session is terminated.
Activity includes CDP connections and live view connections. Defaults to 60
@@ -212,18 +301,25 @@ def create(
check for inactivity every 5 seconds, so the actual timeout behavior you will
see is +/- 5 seconds around the specified value.
+ vaults: Project-scoped vaults to link to the browser session. Links are immutable after
+ creation.
+
+ video_memory: Video memory (VRAM) for a GPU browser session. Requires gpu=true. Defaults to
+ 2GiB, which comes with 4 vCPU and 6GiB memory. 4GiB comes with 8 vCPU and 12GiB
+ memory.
+
viewport: Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
extra_headers: Send extra headers
@@ -237,17 +333,27 @@ def create(
"/browsers",
body=maybe_transform(
{
+ "chrome_policy": chrome_policy,
"extensions": extensions,
"gpu": gpu,
"headless": headless,
"invocation_id": invocation_id,
"kiosk_mode": kiosk_mode,
- "persistence": persistence,
+ "location": location,
+ "memory": memory,
+ "name": name,
+ "network": network,
"profile": profile,
+ "proxy": proxy,
"proxy_id": proxy_id,
+ "region": region,
"start_url": start_url,
"stealth": stealth,
+ "tags": tags,
+ "telemetry": telemetry,
"timeout_seconds": timeout_seconds,
+ "vaults": vaults,
+ "video_memory": video_memory,
"viewport": viewport,
},
browser_create_params.BrowserCreateParams,
@@ -260,7 +366,7 @@ def create(
def retrieve(
self,
- id: str,
+ id_or_name: str,
*,
include_deleted: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -284,10 +390,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("/browsers/{id}", id=id),
+ path_template("/browsers/{id_or_name}", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -302,11 +408,18 @@ def retrieve(
def update(
self,
- id: str,
+ 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,
+ proxy: BrowserProxyConfigParam | Omit = omit,
proxy_id: Optional[str] | Omit = omit,
+ start_url: str | Omit = omit,
+ tags: Optional[TagsParam] | Omit = omit,
+ telemetry: Optional[browser_update_params.Telemetry] | Omit = omit,
viewport: browser_update_params.Viewport | 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.
@@ -320,13 +433,49 @@ def update(
Args:
disable_default_proxy: If true, stealth browsers connect directly instead of using the default stealth
- proxy.
+ 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.
+
+ network: Network configuration changes to apply to the running browser session.
profile: Profile to load into the browser session. Only allowed if the session does not
already have a profile loaded.
+ proxy: Proxy configuration to apply. Omit to leave the current configuration unchanged.
+ Cannot be combined with proxy_id or disable_default_proxy. Set mode to direct to
+ switch to direct egress regardless of stealth. Set mode to default to restore
+ the browser default after using a selected proxy: Kernel's default stealth proxy
+ for a stealth browser, or direct egress for a non-stealth browser. Updating
+ proxy does not change stealth or CAPTCHA solver behavior.
+
proxy_id: ID of the proxy to use. Omit to leave unchanged, set to empty string to remove
- proxy.
+ proxy. Deprecated in favor of proxy.
+
+ start_url: Optional URL to navigate the browser to after applying this update. When a
+ profile is loaded in the same update, this overrides the profile's restored
+ tabs. Navigation is best-effort, so failures do not fail the update. Omit or set
+ to an empty string to leave the current page unchanged.
+
+ tags: User-defined key-value tags for the browser session. Omit to leave unchanged.
+ Provide a map to replace the entire tag set (full replace, not a merge). Set to
+ an empty object ({}) to clear all tags. Up to 50 pairs.
+
+ telemetry: Telemetry configuration. Omit, set to null, or set to an empty object ({}) to
+ leave the existing configuration unchanged. Set enabled to true to enable
+ capture using VM defaults. Set enabled to false to stop capture. Provide browser
+ category settings for per-category updates. Explicitly disabling all four
+ categories also stops capture.
viewport: Viewport configuration to apply to the browser session.
@@ -338,15 +487,22 @@ 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("/browsers/{id}", id=id),
+ path_template("/browsers/{id_or_name}", id_or_name=id_or_name),
body=maybe_transform(
{
"disable_default_proxy": disable_default_proxy,
+ "location": location,
+ "name": name,
+ "network": network,
"profile": profile,
+ "proxy": proxy,
"proxy_id": proxy_id,
+ "start_url": start_url,
+ "tags": tags,
+ "telemetry": telemetry,
"viewport": viewport,
},
browser_update_params.BrowserUpdateParams,
@@ -364,7 +520,9 @@ def list(
limit: int | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
status: Literal["active", "deleted", "all"] | Omit = omit,
+ tags: Dict[str, str] | 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,
@@ -385,11 +543,17 @@ def list(
offset: Number of results to skip. Defaults to 0.
- query: Search browsers by session ID, profile ID, proxy ID, or pool name.
+ query: Search browsers by name, session ID, profile name or ID, proxy ID, or pool name.
+
+ region: Filter sessions by geographic region. Omit to list sessions in all regions.
status: Filter sessions by status. "active" returns only active sessions (default),
"deleted" returns only soft-deleted sessions, "all" returns both.
+ tags: Filter sessions by tag key-value pairs using deepObject style, e.g.
+ ?tags[team]=backend&tags[env]=staging. Multiple pairs are ANDed: a session must
+ match every supplied pair exactly.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -412,7 +576,9 @@ def list(
"limit": limit,
"offset": offset,
"query": query,
+ "region": region,
"status": status,
+ "tags": tags,
},
browser_list_params.BrowserListParams,
),
@@ -420,50 +586,9 @@ def list(
model=BrowserListResponse,
)
- @typing_extensions.deprecated("deprecated")
- def delete(
- self,
- *,
- persistent_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:
- """DEPRECATED: Use DELETE /browsers/{id} instead.
-
- Delete a persistent browser
- session by its persistent_id.
-
- Args:
- persistent_id: Persistent browser identifier
-
- 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
- """
- extra_headers = {"Accept": "*/*", **(extra_headers or {})}
- return self._delete(
- "/browsers",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform({"persistent_id": persistent_id}, browser_delete_params.BrowserDeleteParams),
- ),
- cast_to=NoneType,
- )
-
def curl(
self,
- id: str,
+ id_or_name: str,
*,
url: str,
body: str | Omit = omit,
@@ -504,10 +629,10 @@ def curl(
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("/browsers/{id}/curl", id=id),
+ path_template("/browsers/{id_or_name}/curl", id_or_name=id_or_name),
body=maybe_transform(
{
"url": url,
@@ -585,7 +710,7 @@ def stream(
def delete_by_id(
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.
@@ -595,7 +720,7 @@ def delete_by_id(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Delete a browser session by ID
+ Delete a browser session by ID or name
Args:
extra_headers: Send extra headers
@@ -606,11 +731,11 @@ def delete_by_id(
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("/browsers/{id}", id=id),
+ path_template("/browsers/{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
),
@@ -619,7 +744,7 @@ def delete_by_id(
def load_extensions(
self,
- id: str,
+ id_or_name: str,
*,
extensions: Iterable[browser_load_extensions_params.Extension],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -630,8 +755,8 @@ def load_extensions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Loads one or more unpacked extensions and restarts Chromium on the browser
- instance.
+ Loads one or more unpacked extensions using live CDP activation when eligible.
+ Chromium restarts when enterprise policy requires it or live activation fails.
Args:
extensions: List of extensions to upload and activate
@@ -644,8 +769,8 @@ def load_extensions(
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 {})}
body = deepcopy_with_paths({"extensions": extensions}, [["extensions", "", "zip_file"]])
files = extract_files(cast(Mapping[str, object], body), paths=[["extensions", "", "zip_file"]])
@@ -654,7 +779,7 @@ def load_extensions(
# multipart/form-data; boundary=---abc--
extra_headers["Content-Type"] = "multipart/form-data"
return self._post(
- path_template("/browsers/{id}/extensions", id=id),
+ path_template("/browsers/{id_or_name}/extensions", id_or_name=id_or_name),
body=maybe_transform(body, browser_load_extensions_params.BrowserLoadExtensionsParams),
files=files,
options=make_request_options(
@@ -663,9 +788,81 @@ def load_extensions(
cast_to=NoneType,
)
+ def repl(
+ self,
+ id_or_name: str,
+ *,
+ code: str,
+ reset: bool | Omit = omit,
+ timeout_sec: 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,
+ ) -> BrowserReplResult:
+ """
+ Execute JavaScript in a persistent Node.js runtime inside the browser VM.
+ Top-level bindings, closures, mutations, and dynamically imported modules
+ persist across calls until the REPL is reset or replaced. Start with
+ `repl.help()` to list available methods, or call `repl.help("click")` for
+ detailed help.
+
+ Expression values are ignored. Emit ordered text or image output with
+ `repl.write(...)`, console methods, or `repl.emitImage(...)`. The runtime also
+ exposes browser-control helpers, WebMCP, Patchright, Playwright, and raw CDP.
+
+ Executions are serialized. A timeout, crash, OOM, or protocol failure terminates
+ the REPL and changes its `repl_id`. This is unrestricted code execution inside
+ the browser VM and is not sandboxed.
+
+ Args:
+ code: JavaScript evaluated in a persistent Node.js runtime. Top-level bindings persist
+ until the browser VM's API process exits, the REPL is reset, or the REPL is
+ terminated after a crash or timeout. Static top-level imports are unsupported;
+ use dynamic `import()`. Expression values are ignored; emit output with
+ `repl.write(...)`, console methods, or `repl.emitImage(...)`. May be empty only
+ when `reset` is true.
+
+ reset: Terminate the current REPL, start a fresh one, and then evaluate code.
+
+ timeout_sec: Maximum execution time in seconds. Default is 60.
+
+ 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_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("/browsers/{id_or_name}/repl", id_or_name=id_or_name),
+ body=maybe_transform(
+ {
+ "code": code,
+ "reset": reset,
+ "timeout_sec": timeout_sec,
+ },
+ browser_repl_params.BrowserReplParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BrowserReplResult,
+ )
+
class AsyncBrowsersResource(AsyncAPIResource):
- """Create and manage browser sessions."""
+ @cached_property
+ def telemetry(self) -> AsyncTelemetryResource:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncTelemetryResource(self._client)
@cached_property
def replays(self) -> AsyncReplaysResource:
@@ -689,13 +886,21 @@ def logs(self) -> AsyncLogsResource:
@cached_property
def computer(self) -> AsyncComputerResource:
+ """Control mouse, keyboard, and screen on the browser instance."""
return AsyncComputerResource(self._client)
@cached_property
def playwright(self) -> AsyncPlaywrightResource:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return AsyncPlaywrightResource(self._client)
+ @cached_property
+ def webmcp(self) -> AsyncWebmcpResource:
+ """Discover and invoke native page tools across the browser instance."""
+ return AsyncWebmcpResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncBrowsersResourceWithRawResponse:
"""
@@ -718,17 +923,27 @@ def with_streaming_response(self) -> AsyncBrowsersResourceWithStreamingResponse:
async def create(
self,
*,
+ chrome_policy: Dict[str, object] | Omit = omit,
extensions: Iterable[BrowserExtension] | Omit = omit,
gpu: bool | Omit = omit,
headless: bool | Omit = omit,
invocation_id: str | Omit = omit,
kiosk_mode: bool | Omit = omit,
- persistence: BrowserPersistenceParam | Omit = omit,
+ location: BrowserLocationRequestParam | Omit = omit,
+ memory: BrowserMemoryRequest | Omit = omit,
+ name: str | Omit = omit,
+ network: BrowserNetworkConfigParam | Omit = omit,
profile: BrowserProfile | Omit = omit,
+ proxy: BrowserProxyConfigParam | Omit = omit,
proxy_id: str | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
+ tags: TagsParam | Omit = omit,
+ telemetry: Optional[browser_create_params.Telemetry] | Omit = omit,
timeout_seconds: int | Omit = omit,
+ vaults: Iterable[VaultReferenceParam] | Omit = omit,
+ video_memory: BrowserVideoMemory | Omit = omit,
viewport: BrowserViewport | 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.
@@ -741,10 +956,15 @@ async def create(
Create a new browser session from within an action.
Args:
+ chrome_policy: Custom Chrome enterprise policy overrides applied to this browser session. Keys
+ are Chrome enterprise policy names; values must match their expected types.
+ Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
+ https://chromeenterprise.google/policies/
+
extensions: List of browser extensions to load into the session. Provide each by id or name.
gpu: If true, enables GPU acceleration for the browser session. Requires Start-Up or
- Enterprise plan and headless=false.
+ Enterprise plan, headless=false, and region=us-east.
headless: If true, launches the browser using a headless image (no VNC/GUI). Defaults to
false.
@@ -754,23 +974,58 @@ async def create(
kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
view.
- persistence: DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.
+ 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.
+
+ name: Optional human-readable name for the browser session, used to find it later in
+ the dashboard. Must be unique among active sessions within the project. Can be
+ changed later via PATCH /browsers/{id_or_name}.
+
+ network: Network configuration for the browser session. Only allowed_hosts can be changed
+ after creation, with PATCH /browsers/{id_or_name}.
profile: Profile selection for the browser session. Provide either id or name. If
specified, the matching profile will be loaded into the browser session.
Profiles must be created beforehand.
- proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
- belonging to the caller's org.
-
- start_url: Optional URL to navigate to immediately after the browser is created.
- Best-effort: failures to navigate do not fail browser creation. Any pre-existing
- tabs are reduced to a single tab which is then navigated. Accepts any URL
- Chromium can resolve, including chrome:// pages. Ignored when reusing an
- existing persistent session.
-
- stealth: If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
+ proxy: Proxy configuration for the browser session. Cannot be combined with proxy_id.
+ Omit to use the browser default: stealth browsers use Kernel's default stealth
+ proxy, while non-stealth browsers use direct egress. Set mode to direct to force
+ direct egress regardless of stealth. Set mode to default to explicitly use the
+ browser default: Kernel's default stealth proxy when stealth=true, or direct
+ egress when stealth=false. Select id or name to use that proxy regardless of
+ stealth. Proxy selection does not change stealth or CAPTCHA solver behavior.
+
+ proxy_id: Optional proxy to associate to the browser session. Must reference a proxy in
+ the same project as the browser session. Deprecated in favor of proxy.
+
+ region: Geographic region for the browser session. It is fixed once the session is
+ created. Region selection requires a Start-Up or Enterprise plan, defaults to
+ us-east when omitted on create.
+
+ start_url: Optional URL to open when the browser session is created. Navigation is
+ best-effort, so navigation failures do not prevent the session from being
+ created.
+
+ stealth: If true, launches the browser in stealth mode and enables the CAPTCHA solver.
+ Defaults to false. When proxy is omitted, stealth browsers use Kernel's default
+ stealth proxy and non-stealth browsers use direct egress. An explicit proxy
+ configuration changes only egress; it does not enable or disable stealth or the
+ CAPTCHA solver.
+
+ tags: Optional user-defined key-value tags for the browser session, used to find and
+ group sessions later. Can be changed later via PATCH /browsers/{id_or_name}. Up
+ to 50 pairs.
+
+ telemetry: Telemetry configuration for the browser session. Set enabled to true to start
+ capture using VM defaults, or provide browser category settings. If omitted,
+ null, set to an empty object ({}), set to enabled: false without browser
+ category settings, or all four categories are explicitly disabled, capture is
+ not started.
timeout_seconds: The number of seconds of inactivity before the browser session is terminated.
Activity includes CDP connections and live view connections. Defaults to 60
@@ -778,18 +1033,25 @@ async def create(
check for inactivity every 5 seconds, so the actual timeout behavior you will
see is +/- 5 seconds around the specified value.
+ vaults: Project-scoped vaults to link to the browser session. Links are immutable after
+ creation.
+
+ video_memory: Video memory (VRAM) for a GPU browser session. Requires gpu=true. Defaults to
+ 2GiB, which comes with 4 vCPU and 6GiB memory. 4GiB comes with 8 vCPU and 12GiB
+ memory.
+
viewport: Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
extra_headers: Send extra headers
@@ -803,17 +1065,27 @@ async def create(
"/browsers",
body=await async_maybe_transform(
{
+ "chrome_policy": chrome_policy,
"extensions": extensions,
"gpu": gpu,
"headless": headless,
"invocation_id": invocation_id,
"kiosk_mode": kiosk_mode,
- "persistence": persistence,
+ "location": location,
+ "memory": memory,
+ "name": name,
+ "network": network,
"profile": profile,
+ "proxy": proxy,
"proxy_id": proxy_id,
+ "region": region,
"start_url": start_url,
"stealth": stealth,
+ "tags": tags,
+ "telemetry": telemetry,
"timeout_seconds": timeout_seconds,
+ "vaults": vaults,
+ "video_memory": video_memory,
"viewport": viewport,
},
browser_create_params.BrowserCreateParams,
@@ -826,7 +1098,7 @@ async def create(
async def retrieve(
self,
- id: str,
+ id_or_name: str,
*,
include_deleted: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -850,10 +1122,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("/browsers/{id}", id=id),
+ path_template("/browsers/{id_or_name}", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -868,11 +1140,18 @@ async def retrieve(
async def update(
self,
- id: str,
+ 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,
+ proxy: BrowserProxyConfigParam | Omit = omit,
proxy_id: Optional[str] | Omit = omit,
+ start_url: str | Omit = omit,
+ tags: Optional[TagsParam] | Omit = omit,
+ telemetry: Optional[browser_update_params.Telemetry] | Omit = omit,
viewport: browser_update_params.Viewport | 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.
@@ -886,13 +1165,49 @@ async def update(
Args:
disable_default_proxy: If true, stealth browsers connect directly instead of using the default stealth
- proxy.
+ 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.
+
+ network: Network configuration changes to apply to the running browser session.
profile: Profile to load into the browser session. Only allowed if the session does not
already have a profile loaded.
+ proxy: Proxy configuration to apply. Omit to leave the current configuration unchanged.
+ Cannot be combined with proxy_id or disable_default_proxy. Set mode to direct to
+ switch to direct egress regardless of stealth. Set mode to default to restore
+ the browser default after using a selected proxy: Kernel's default stealth proxy
+ for a stealth browser, or direct egress for a non-stealth browser. Updating
+ proxy does not change stealth or CAPTCHA solver behavior.
+
proxy_id: ID of the proxy to use. Omit to leave unchanged, set to empty string to remove
- proxy.
+ proxy. Deprecated in favor of proxy.
+
+ start_url: Optional URL to navigate the browser to after applying this update. When a
+ profile is loaded in the same update, this overrides the profile's restored
+ tabs. Navigation is best-effort, so failures do not fail the update. Omit or set
+ to an empty string to leave the current page unchanged.
+
+ tags: User-defined key-value tags for the browser session. Omit to leave unchanged.
+ Provide a map to replace the entire tag set (full replace, not a merge). Set to
+ an empty object ({}) to clear all tags. Up to 50 pairs.
+
+ telemetry: Telemetry configuration. Omit, set to null, or set to an empty object ({}) to
+ leave the existing configuration unchanged. Set enabled to true to enable
+ capture using VM defaults. Set enabled to false to stop capture. Provide browser
+ category settings for per-category updates. Explicitly disabling all four
+ categories also stops capture.
viewport: Viewport configuration to apply to the browser session.
@@ -904,15 +1219,22 @@ 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("/browsers/{id}", id=id),
+ path_template("/browsers/{id_or_name}", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"disable_default_proxy": disable_default_proxy,
+ "location": location,
+ "name": name,
+ "network": network,
"profile": profile,
+ "proxy": proxy,
"proxy_id": proxy_id,
+ "start_url": start_url,
+ "tags": tags,
+ "telemetry": telemetry,
"viewport": viewport,
},
browser_update_params.BrowserUpdateParams,
@@ -930,7 +1252,9 @@ def list(
limit: int | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
status: Literal["active", "deleted", "all"] | Omit = omit,
+ tags: Dict[str, str] | 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,
@@ -951,11 +1275,17 @@ def list(
offset: Number of results to skip. Defaults to 0.
- query: Search browsers by session ID, profile ID, proxy ID, or pool name.
+ query: Search browsers by name, session ID, profile name or ID, proxy ID, or pool name.
+
+ region: Filter sessions by geographic region. Omit to list sessions in all regions.
status: Filter sessions by status. "active" returns only active sessions (default),
"deleted" returns only soft-deleted sessions, "all" returns both.
+ tags: Filter sessions by tag key-value pairs using deepObject style, e.g.
+ ?tags[team]=backend&tags[env]=staging. Multiple pairs are ANDed: a session must
+ match every supplied pair exactly.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -978,7 +1308,9 @@ def list(
"limit": limit,
"offset": offset,
"query": query,
+ "region": region,
"status": status,
+ "tags": tags,
},
browser_list_params.BrowserListParams,
),
@@ -986,52 +1318,9 @@ def list(
model=BrowserListResponse,
)
- @typing_extensions.deprecated("deprecated")
- async def delete(
- self,
- *,
- persistent_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:
- """DEPRECATED: Use DELETE /browsers/{id} instead.
-
- Delete a persistent browser
- session by its persistent_id.
-
- Args:
- persistent_id: Persistent browser identifier
-
- 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
- """
- extra_headers = {"Accept": "*/*", **(extra_headers or {})}
- return await self._delete(
- "/browsers",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"persistent_id": persistent_id}, browser_delete_params.BrowserDeleteParams
- ),
- ),
- cast_to=NoneType,
- )
-
async def curl(
self,
- id: str,
+ id_or_name: str,
*,
url: str,
body: str | Omit = omit,
@@ -1072,10 +1361,10 @@ async def curl(
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("/browsers/{id}/curl", id=id),
+ path_template("/browsers/{id_or_name}/curl", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"url": url,
@@ -1153,7 +1442,7 @@ async def stream(
async def delete_by_id(
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.
@@ -1163,7 +1452,7 @@ async def delete_by_id(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Delete a browser session by ID
+ Delete a browser session by ID or name
Args:
extra_headers: Send extra headers
@@ -1174,11 +1463,11 @@ async def delete_by_id(
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("/browsers/{id}", id=id),
+ path_template("/browsers/{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
),
@@ -1187,7 +1476,7 @@ async def delete_by_id(
async def load_extensions(
self,
- id: str,
+ id_or_name: str,
*,
extensions: Iterable[browser_load_extensions_params.Extension],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -1198,8 +1487,8 @@ async def load_extensions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Loads one or more unpacked extensions and restarts Chromium on the browser
- instance.
+ Loads one or more unpacked extensions using live CDP activation when eligible.
+ Chromium restarts when enterprise policy requires it or live activation fails.
Args:
extensions: List of extensions to upload and activate
@@ -1212,8 +1501,8 @@ async def load_extensions(
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 {})}
body = deepcopy_with_paths({"extensions": extensions}, [["extensions", "", "zip_file"]])
files = extract_files(cast(Mapping[str, object], body), paths=[["extensions", "", "zip_file"]])
@@ -1222,7 +1511,7 @@ async def load_extensions(
# multipart/form-data; boundary=---abc--
extra_headers["Content-Type"] = "multipart/form-data"
return await self._post(
- path_template("/browsers/{id}/extensions", id=id),
+ path_template("/browsers/{id_or_name}/extensions", id_or_name=id_or_name),
body=await async_maybe_transform(body, browser_load_extensions_params.BrowserLoadExtensionsParams),
files=files,
options=make_request_options(
@@ -1231,6 +1520,73 @@ async def load_extensions(
cast_to=NoneType,
)
+ async def repl(
+ self,
+ id_or_name: str,
+ *,
+ code: str,
+ reset: bool | Omit = omit,
+ timeout_sec: 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,
+ ) -> BrowserReplResult:
+ """
+ Execute JavaScript in a persistent Node.js runtime inside the browser VM.
+ Top-level bindings, closures, mutations, and dynamically imported modules
+ persist across calls until the REPL is reset or replaced. Start with
+ `repl.help()` to list available methods, or call `repl.help("click")` for
+ detailed help.
+
+ Expression values are ignored. Emit ordered text or image output with
+ `repl.write(...)`, console methods, or `repl.emitImage(...)`. The runtime also
+ exposes browser-control helpers, WebMCP, Patchright, Playwright, and raw CDP.
+
+ Executions are serialized. A timeout, crash, OOM, or protocol failure terminates
+ the REPL and changes its `repl_id`. This is unrestricted code execution inside
+ the browser VM and is not sandboxed.
+
+ Args:
+ code: JavaScript evaluated in a persistent Node.js runtime. Top-level bindings persist
+ until the browser VM's API process exits, the REPL is reset, or the REPL is
+ terminated after a crash or timeout. Static top-level imports are unsupported;
+ use dynamic `import()`. Expression values are ignored; emit output with
+ `repl.write(...)`, console methods, or `repl.emitImage(...)`. May be empty only
+ when `reset` is true.
+
+ reset: Terminate the current REPL, start a fresh one, and then evaluate code.
+
+ timeout_sec: Maximum execution time in seconds. Default is 60.
+
+ 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_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("/browsers/{id_or_name}/repl", id_or_name=id_or_name),
+ body=await async_maybe_transform(
+ {
+ "code": code,
+ "reset": reset,
+ "timeout_sec": timeout_sec,
+ },
+ browser_repl_params.BrowserReplParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BrowserReplResult,
+ )
+
class BrowsersResourceWithRawResponse:
def __init__(self, browsers: BrowsersResource) -> None:
@@ -1248,11 +1604,6 @@ def __init__(self, browsers: BrowsersResource) -> None:
self.list = to_raw_response_wrapper(
browsers.list,
)
- self.delete = ( # pyright: ignore[reportDeprecated]
- to_raw_response_wrapper(
- browsers.delete, # pyright: ignore[reportDeprecated],
- )
- )
self.curl = to_raw_response_wrapper(
browsers.curl,
)
@@ -1262,6 +1613,16 @@ def __init__(self, browsers: BrowsersResource) -> None:
self.load_extensions = to_raw_response_wrapper(
browsers.load_extensions,
)
+ self.repl = to_raw_response_wrapper(
+ browsers.repl,
+ )
+
+ @cached_property
+ def telemetry(self) -> TelemetryResourceWithRawResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return TelemetryResourceWithRawResponse(self._browsers.telemetry)
@cached_property
def replays(self) -> ReplaysResourceWithRawResponse:
@@ -1285,13 +1646,21 @@ def logs(self) -> LogsResourceWithRawResponse:
@cached_property
def computer(self) -> ComputerResourceWithRawResponse:
+ """Control mouse, keyboard, and screen on the browser instance."""
return ComputerResourceWithRawResponse(self._browsers.computer)
@cached_property
def playwright(self) -> PlaywrightResourceWithRawResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return PlaywrightResourceWithRawResponse(self._browsers.playwright)
+ @cached_property
+ def webmcp(self) -> WebmcpResourceWithRawResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return WebmcpResourceWithRawResponse(self._browsers.webmcp)
+
class AsyncBrowsersResourceWithRawResponse:
def __init__(self, browsers: AsyncBrowsersResource) -> None:
@@ -1309,11 +1678,6 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
self.list = async_to_raw_response_wrapper(
browsers.list,
)
- self.delete = ( # pyright: ignore[reportDeprecated]
- async_to_raw_response_wrapper(
- browsers.delete, # pyright: ignore[reportDeprecated],
- )
- )
self.curl = async_to_raw_response_wrapper(
browsers.curl,
)
@@ -1323,6 +1687,16 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
self.load_extensions = async_to_raw_response_wrapper(
browsers.load_extensions,
)
+ self.repl = async_to_raw_response_wrapper(
+ browsers.repl,
+ )
+
+ @cached_property
+ def telemetry(self) -> AsyncTelemetryResourceWithRawResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncTelemetryResourceWithRawResponse(self._browsers.telemetry)
@cached_property
def replays(self) -> AsyncReplaysResourceWithRawResponse:
@@ -1346,13 +1720,21 @@ def logs(self) -> AsyncLogsResourceWithRawResponse:
@cached_property
def computer(self) -> AsyncComputerResourceWithRawResponse:
+ """Control mouse, keyboard, and screen on the browser instance."""
return AsyncComputerResourceWithRawResponse(self._browsers.computer)
@cached_property
def playwright(self) -> AsyncPlaywrightResourceWithRawResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return AsyncPlaywrightResourceWithRawResponse(self._browsers.playwright)
+ @cached_property
+ def webmcp(self) -> AsyncWebmcpResourceWithRawResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return AsyncWebmcpResourceWithRawResponse(self._browsers.webmcp)
+
class BrowsersResourceWithStreamingResponse:
def __init__(self, browsers: BrowsersResource) -> None:
@@ -1370,11 +1752,6 @@ def __init__(self, browsers: BrowsersResource) -> None:
self.list = to_streamed_response_wrapper(
browsers.list,
)
- self.delete = ( # pyright: ignore[reportDeprecated]
- to_streamed_response_wrapper(
- browsers.delete, # pyright: ignore[reportDeprecated],
- )
- )
self.curl = to_streamed_response_wrapper(
browsers.curl,
)
@@ -1384,6 +1761,16 @@ def __init__(self, browsers: BrowsersResource) -> None:
self.load_extensions = to_streamed_response_wrapper(
browsers.load_extensions,
)
+ self.repl = to_streamed_response_wrapper(
+ browsers.repl,
+ )
+
+ @cached_property
+ def telemetry(self) -> TelemetryResourceWithStreamingResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return TelemetryResourceWithStreamingResponse(self._browsers.telemetry)
@cached_property
def replays(self) -> ReplaysResourceWithStreamingResponse:
@@ -1407,13 +1794,21 @@ def logs(self) -> LogsResourceWithStreamingResponse:
@cached_property
def computer(self) -> ComputerResourceWithStreamingResponse:
+ """Control mouse, keyboard, and screen on the browser instance."""
return ComputerResourceWithStreamingResponse(self._browsers.computer)
@cached_property
def playwright(self) -> PlaywrightResourceWithStreamingResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return PlaywrightResourceWithStreamingResponse(self._browsers.playwright)
+ @cached_property
+ def webmcp(self) -> WebmcpResourceWithStreamingResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return WebmcpResourceWithStreamingResponse(self._browsers.webmcp)
+
class AsyncBrowsersResourceWithStreamingResponse:
def __init__(self, browsers: AsyncBrowsersResource) -> None:
@@ -1431,11 +1826,6 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
self.list = async_to_streamed_response_wrapper(
browsers.list,
)
- self.delete = ( # pyright: ignore[reportDeprecated]
- async_to_streamed_response_wrapper(
- browsers.delete, # pyright: ignore[reportDeprecated],
- )
- )
self.curl = async_to_streamed_response_wrapper(
browsers.curl,
)
@@ -1445,6 +1835,16 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
self.load_extensions = async_to_streamed_response_wrapper(
browsers.load_extensions,
)
+ self.repl = async_to_streamed_response_wrapper(
+ browsers.repl,
+ )
+
+ @cached_property
+ def telemetry(self) -> AsyncTelemetryResourceWithStreamingResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncTelemetryResourceWithStreamingResponse(self._browsers.telemetry)
@cached_property
def replays(self) -> AsyncReplaysResourceWithStreamingResponse:
@@ -1468,9 +1868,17 @@ def logs(self) -> AsyncLogsResourceWithStreamingResponse:
@cached_property
def computer(self) -> AsyncComputerResourceWithStreamingResponse:
+ """Control mouse, keyboard, and screen on the browser instance."""
return AsyncComputerResourceWithStreamingResponse(self._browsers.computer)
@cached_property
def playwright(self) -> AsyncPlaywrightResourceWithStreamingResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return AsyncPlaywrightResourceWithStreamingResponse(self._browsers.playwright)
+
+ @cached_property
+ def webmcp(self) -> AsyncWebmcpResourceWithStreamingResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return AsyncWebmcpResourceWithStreamingResponse(self._browsers.webmcp)
diff --git a/src/kernel/resources/browsers/computer.py b/src/kernel/resources/browsers/computer.py
index 54b638e5..b40ac897 100644
--- a/src/kernel/resources/browsers/computer.py
+++ b/src/kernel/resources/browsers/computer.py
@@ -46,6 +46,8 @@
class ComputerResource(SyncAPIResource):
+ """Control mouse, keyboard, and screen on the browser instance."""
+
@cached_property
def with_raw_response(self) -> ComputerResourceWithRawResponse:
"""
@@ -67,7 +69,7 @@ def with_streaming_response(self) -> ComputerResourceWithStreamingResponse:
def batch(
self,
- id: str,
+ id_or_name: str,
*,
actions: Iterable[computer_batch_params.Action],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -93,11 +95,11 @@ def batch(
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._post(
- path_template("/browsers/{id}/computer/batch", id=id),
+ path_template("/browsers/{id_or_name}/computer/batch", id_or_name=id_or_name),
body=maybe_transform({"actions": actions}, computer_batch_params.ComputerBatchParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -107,7 +109,7 @@ def batch(
def capture_screenshot(
self,
- id: str,
+ id_or_name: str,
*,
region: computer_capture_screenshot_params.Region | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -129,11 +131,11 @@ def capture_screenshot(
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": "image/png", **(extra_headers or {})}
return self._post(
- path_template("/browsers/{id}/computer/screenshot", id=id),
+ path_template("/browsers/{id_or_name}/computer/screenshot", id_or_name=id_or_name),
body=maybe_transform(
{"region": region}, computer_capture_screenshot_params.ComputerCaptureScreenshotParams
),
@@ -145,7 +147,7 @@ def capture_screenshot(
def click_mouse(
self,
- id: str,
+ id_or_name: str,
*,
x: int,
y: int,
@@ -184,11 +186,11 @@ def click_mouse(
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._post(
- path_template("/browsers/{id}/computer/click_mouse", id=id),
+ path_template("/browsers/{id_or_name}/computer/click_mouse", id_or_name=id_or_name),
body=maybe_transform(
{
"x": x,
@@ -208,7 +210,7 @@ def click_mouse(
def drag_mouse(
self,
- id: str,
+ id_or_name: str,
*,
path: Iterable[Iterable[int]],
button: Literal["left", "middle", "right"] | Omit = omit,
@@ -257,11 +259,11 @@ def drag_mouse(
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._post(
- path_template("/browsers/{id}/computer/drag_mouse", id=id),
+ path_template("/browsers/{id_or_name}/computer/drag_mouse", id_or_name=id_or_name),
body=maybe_transform(
{
"path": path,
@@ -283,7 +285,7 @@ def drag_mouse(
def get_mouse_position(
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.
@@ -304,10 +306,10 @@ def get_mouse_position(
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("/browsers/{id}/computer/get_mouse_position", id=id),
+ path_template("/browsers/{id_or_name}/computer/get_mouse_position", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -316,7 +318,7 @@ def get_mouse_position(
def move_mouse(
self,
- id: str,
+ id_or_name: str,
*,
x: int,
y: int,
@@ -353,11 +355,11 @@ def move_mouse(
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._post(
- path_template("/browsers/{id}/computer/move_mouse", id=id),
+ path_template("/browsers/{id_or_name}/computer/move_mouse", id_or_name=id_or_name),
body=maybe_transform(
{
"x": x,
@@ -376,7 +378,7 @@ def move_mouse(
def press_key(
self,
- id: str,
+ id_or_name: str,
*,
keys: SequenceNotStr[str],
duration: int | Omit = omit,
@@ -395,7 +397,9 @@ def press_key(
keys: List of key symbols to press. Each item should be a key symbol supported by
xdotool (see X11 keysym definitions). Examples include "Return", "Shift",
"Ctrl", "Alt", "F5". Items in this list could also be combinations, e.g.
- "Ctrl+t" or "Ctrl+Shift+Tab".
+ "Ctrl+t" or "Ctrl+Shift+Tab". Use X11 names for punctuation in combinations,
+ such as "Ctrl+minus" or "Ctrl+plus". A literal hyphen is also accepted as an
+ alias, so "Ctrl+-" is normalized to "Ctrl+minus".
duration: Duration to hold the keys down in milliseconds. If omitted or 0, keys are
tapped.
@@ -410,11 +414,11 @@ def press_key(
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._post(
- path_template("/browsers/{id}/computer/press_key", id=id),
+ path_template("/browsers/{id_or_name}/computer/press_key", id_or_name=id_or_name),
body=maybe_transform(
{
"keys": keys,
@@ -431,7 +435,7 @@ def press_key(
def read_clipboard(
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.
@@ -452,10 +456,10 @@ def read_clipboard(
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("/browsers/{id}/computer/clipboard/read", id=id),
+ path_template("/browsers/{id_or_name}/computer/clipboard/read", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -464,7 +468,7 @@ def read_clipboard(
def scroll(
self,
- id: str,
+ id_or_name: str,
*,
x: int,
y: int,
@@ -502,11 +506,11 @@ def scroll(
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._post(
- path_template("/browsers/{id}/computer/scroll", id=id),
+ path_template("/browsers/{id_or_name}/computer/scroll", id_or_name=id_or_name),
body=maybe_transform(
{
"x": x,
@@ -525,7 +529,7 @@ def scroll(
def set_cursor_visibility(
self,
- id: str,
+ id_or_name: str,
*,
hidden: bool,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -549,10 +553,10 @@ def set_cursor_visibility(
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("/browsers/{id}/computer/cursor", id=id),
+ path_template("/browsers/{id_or_name}/computer/cursor", id_or_name=id_or_name),
body=maybe_transform(
{"hidden": hidden}, computer_set_cursor_visibility_params.ComputerSetCursorVisibilityParams
),
@@ -564,7 +568,7 @@ def set_cursor_visibility(
def type_text(
self,
- id: str,
+ id_or_name: str,
*,
text: str,
delay: int | Omit = omit,
@@ -591,11 +595,11 @@ def type_text(
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._post(
- path_template("/browsers/{id}/computer/type", id=id),
+ path_template("/browsers/{id_or_name}/computer/type", id_or_name=id_or_name),
body=maybe_transform(
{
"text": text,
@@ -611,7 +615,7 @@ def type_text(
def write_clipboard(
self,
- id: str,
+ id_or_name: str,
*,
text: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -635,11 +639,11 @@ def write_clipboard(
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._post(
- path_template("/browsers/{id}/computer/clipboard/write", id=id),
+ path_template("/browsers/{id_or_name}/computer/clipboard/write", id_or_name=id_or_name),
body=maybe_transform({"text": text}, computer_write_clipboard_params.ComputerWriteClipboardParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -649,6 +653,8 @@ def write_clipboard(
class AsyncComputerResource(AsyncAPIResource):
+ """Control mouse, keyboard, and screen on the browser instance."""
+
@cached_property
def with_raw_response(self) -> AsyncComputerResourceWithRawResponse:
"""
@@ -670,7 +676,7 @@ def with_streaming_response(self) -> AsyncComputerResourceWithStreamingResponse:
async def batch(
self,
- id: str,
+ id_or_name: str,
*,
actions: Iterable[computer_batch_params.Action],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -696,11 +702,11 @@ async def batch(
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._post(
- path_template("/browsers/{id}/computer/batch", id=id),
+ path_template("/browsers/{id_or_name}/computer/batch", id_or_name=id_or_name),
body=await async_maybe_transform({"actions": actions}, computer_batch_params.ComputerBatchParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -710,7 +716,7 @@ async def batch(
async def capture_screenshot(
self,
- id: str,
+ id_or_name: str,
*,
region: computer_capture_screenshot_params.Region | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -732,11 +738,11 @@ async def capture_screenshot(
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": "image/png", **(extra_headers or {})}
return await self._post(
- path_template("/browsers/{id}/computer/screenshot", id=id),
+ path_template("/browsers/{id_or_name}/computer/screenshot", id_or_name=id_or_name),
body=await async_maybe_transform(
{"region": region}, computer_capture_screenshot_params.ComputerCaptureScreenshotParams
),
@@ -748,7 +754,7 @@ async def capture_screenshot(
async def click_mouse(
self,
- id: str,
+ id_or_name: str,
*,
x: int,
y: int,
@@ -787,11 +793,11 @@ async def click_mouse(
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._post(
- path_template("/browsers/{id}/computer/click_mouse", id=id),
+ path_template("/browsers/{id_or_name}/computer/click_mouse", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"x": x,
@@ -811,7 +817,7 @@ async def click_mouse(
async def drag_mouse(
self,
- id: str,
+ id_or_name: str,
*,
path: Iterable[Iterable[int]],
button: Literal["left", "middle", "right"] | Omit = omit,
@@ -860,11 +866,11 @@ async def drag_mouse(
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._post(
- path_template("/browsers/{id}/computer/drag_mouse", id=id),
+ path_template("/browsers/{id_or_name}/computer/drag_mouse", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"path": path,
@@ -886,7 +892,7 @@ async def drag_mouse(
async def get_mouse_position(
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.
@@ -907,10 +913,10 @@ async def get_mouse_position(
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("/browsers/{id}/computer/get_mouse_position", id=id),
+ path_template("/browsers/{id_or_name}/computer/get_mouse_position", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -919,7 +925,7 @@ async def get_mouse_position(
async def move_mouse(
self,
- id: str,
+ id_or_name: str,
*,
x: int,
y: int,
@@ -956,11 +962,11 @@ async def move_mouse(
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._post(
- path_template("/browsers/{id}/computer/move_mouse", id=id),
+ path_template("/browsers/{id_or_name}/computer/move_mouse", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"x": x,
@@ -979,7 +985,7 @@ async def move_mouse(
async def press_key(
self,
- id: str,
+ id_or_name: str,
*,
keys: SequenceNotStr[str],
duration: int | Omit = omit,
@@ -998,7 +1004,9 @@ async def press_key(
keys: List of key symbols to press. Each item should be a key symbol supported by
xdotool (see X11 keysym definitions). Examples include "Return", "Shift",
"Ctrl", "Alt", "F5". Items in this list could also be combinations, e.g.
- "Ctrl+t" or "Ctrl+Shift+Tab".
+ "Ctrl+t" or "Ctrl+Shift+Tab". Use X11 names for punctuation in combinations,
+ such as "Ctrl+minus" or "Ctrl+plus". A literal hyphen is also accepted as an
+ alias, so "Ctrl+-" is normalized to "Ctrl+minus".
duration: Duration to hold the keys down in milliseconds. If omitted or 0, keys are
tapped.
@@ -1013,11 +1021,11 @@ async def press_key(
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._post(
- path_template("/browsers/{id}/computer/press_key", id=id),
+ path_template("/browsers/{id_or_name}/computer/press_key", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"keys": keys,
@@ -1034,7 +1042,7 @@ async def press_key(
async def read_clipboard(
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.
@@ -1055,10 +1063,10 @@ async def read_clipboard(
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("/browsers/{id}/computer/clipboard/read", id=id),
+ path_template("/browsers/{id_or_name}/computer/clipboard/read", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -1067,7 +1075,7 @@ async def read_clipboard(
async def scroll(
self,
- id: str,
+ id_or_name: str,
*,
x: int,
y: int,
@@ -1105,11 +1113,11 @@ async def scroll(
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._post(
- path_template("/browsers/{id}/computer/scroll", id=id),
+ path_template("/browsers/{id_or_name}/computer/scroll", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"x": x,
@@ -1128,7 +1136,7 @@ async def scroll(
async def set_cursor_visibility(
self,
- id: str,
+ id_or_name: str,
*,
hidden: bool,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -1152,10 +1160,10 @@ async def set_cursor_visibility(
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("/browsers/{id}/computer/cursor", id=id),
+ path_template("/browsers/{id_or_name}/computer/cursor", id_or_name=id_or_name),
body=await async_maybe_transform(
{"hidden": hidden}, computer_set_cursor_visibility_params.ComputerSetCursorVisibilityParams
),
@@ -1167,7 +1175,7 @@ async def set_cursor_visibility(
async def type_text(
self,
- id: str,
+ id_or_name: str,
*,
text: str,
delay: int | Omit = omit,
@@ -1194,11 +1202,11 @@ async def type_text(
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._post(
- path_template("/browsers/{id}/computer/type", id=id),
+ path_template("/browsers/{id_or_name}/computer/type", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"text": text,
@@ -1214,7 +1222,7 @@ async def type_text(
async def write_clipboard(
self,
- id: str,
+ id_or_name: str,
*,
text: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -1238,11 +1246,11 @@ async def write_clipboard(
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._post(
- path_template("/browsers/{id}/computer/clipboard/write", id=id),
+ path_template("/browsers/{id_or_name}/computer/clipboard/write", id_or_name=id_or_name),
body=await async_maybe_transform(
{"text": text}, computer_write_clipboard_params.ComputerWriteClipboardParams
),
diff --git a/src/kernel/resources/browsers/fs/fs.py b/src/kernel/resources/browsers/fs/fs.py
index 17149d51..7496287d 100644
--- a/src/kernel/resources/browsers/fs/fs.py
+++ b/src/kernel/resources/browsers/fs/fs.py
@@ -48,6 +48,7 @@
async_to_custom_streamed_response_wrapper,
)
from ...._base_client import make_request_options
+from ....lib.multipart import indexed_multipart_body
from ....types.browsers import (
f_move_params,
f_upload_params,
@@ -97,7 +98,7 @@ def with_streaming_response(self) -> FsResourceWithStreamingResponse:
def create_directory(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
mode: str | Omit = omit,
@@ -124,11 +125,11 @@ def create_directory(
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._put(
- path_template("/browsers/{id}/fs/create_directory", id=id),
+ path_template("/browsers/{id_or_name}/fs/create_directory", id_or_name=id_or_name),
body=maybe_transform(
{
"path": path,
@@ -144,7 +145,7 @@ def create_directory(
def delete_directory(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -168,11 +169,11 @@ def delete_directory(
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._put(
- path_template("/browsers/{id}/fs/delete_directory", id=id),
+ path_template("/browsers/{id_or_name}/fs/delete_directory", id_or_name=id_or_name),
body=maybe_transform({"path": path}, f_delete_directory_params.FDeleteDirectoryParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -182,7 +183,7 @@ def delete_directory(
def delete_file(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -206,11 +207,11 @@ def delete_file(
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._put(
- path_template("/browsers/{id}/fs/delete_file", id=id),
+ path_template("/browsers/{id_or_name}/fs/delete_file", id_or_name=id_or_name),
body=maybe_transform({"path": path}, f_delete_file_params.FDeleteFileParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -220,7 +221,7 @@ def delete_file(
def download_dir_zip(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -244,11 +245,11 @@ def download_dir_zip(
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": "application/zip", **(extra_headers or {})}
return self._get(
- path_template("/browsers/{id}/fs/download_dir_zip", id=id),
+ path_template("/browsers/{id_or_name}/fs/download_dir_zip", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -261,7 +262,7 @@ def download_dir_zip(
def file_info(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -285,10 +286,10 @@ def file_info(
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("/browsers/{id}/fs/file_info", id=id),
+ path_template("/browsers/{id_or_name}/fs/file_info", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -301,7 +302,7 @@ def file_info(
def list_files(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -325,10 +326,10 @@ def list_files(
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("/browsers/{id}/fs/list_files", id=id),
+ path_template("/browsers/{id_or_name}/fs/list_files", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -341,7 +342,7 @@ def list_files(
def move(
self,
- id: str,
+ id_or_name: str,
*,
dest_path: str,
src_path: str,
@@ -368,11 +369,11 @@ def move(
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._put(
- path_template("/browsers/{id}/fs/move", id=id),
+ path_template("/browsers/{id_or_name}/fs/move", id_or_name=id_or_name),
body=maybe_transform(
{
"dest_path": dest_path,
@@ -388,7 +389,7 @@ def move(
def read_file(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -412,11 +413,11 @@ def read_file(
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": "application/octet-stream", **(extra_headers or {})}
return self._get(
- path_template("/browsers/{id}/fs/read_file", id=id),
+ path_template("/browsers/{id_or_name}/fs/read_file", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -429,7 +430,7 @@ def read_file(
def set_file_permissions(
self,
- id: str,
+ id_or_name: str,
*,
mode: str,
path: str,
@@ -462,11 +463,11 @@ def set_file_permissions(
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._put(
- path_template("/browsers/{id}/fs/set_file_permissions", id=id),
+ path_template("/browsers/{id_or_name}/fs/set_file_permissions", id_or_name=id_or_name),
body=maybe_transform(
{
"mode": mode,
@@ -484,7 +485,7 @@ def set_file_permissions(
def upload(
self,
- id: str,
+ id_or_name: str,
*,
files: Iterable[f_upload_params.File],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -506,18 +507,23 @@ def upload(
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 {})}
- body = deepcopy_with_paths({"files": files}, [["files", "", "file"]])
- extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "", "file"]])
+ body = deepcopy_with_paths({"files": list(files)}, [["files", "", "file"]])
+ # The remote filesystem pairs each file part with the sibling fields of the
+ # same array entry, so both halves of the form use indexed names
+ # (`files[0][file]`, `files[0][dest_path]`).
+ extracted_files = extract_files(
+ cast(Mapping[str, object], body), paths=[["files", "", "file"]], array_format="indices"
+ )
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
extra_headers["Content-Type"] = "multipart/form-data"
return self._post(
- path_template("/browsers/{id}/fs/upload", id=id),
- body=maybe_transform(body, f_upload_params.FUploadParams),
+ path_template("/browsers/{id_or_name}/fs/upload", id_or_name=id_or_name),
+ body=indexed_multipart_body(maybe_transform(body, f_upload_params.FUploadParams)),
files=extracted_files,
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -527,7 +533,7 @@ def upload(
def upload_zip(
self,
- id: str,
+ id_or_name: str,
*,
dest_path: str,
zip_file: FileTypes,
@@ -552,8 +558,8 @@ def upload_zip(
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 {})}
body = deepcopy_with_paths(
{
@@ -568,7 +574,7 @@ def upload_zip(
# multipart/form-data; boundary=---abc--
extra_headers["Content-Type"] = "multipart/form-data"
return self._post(
- path_template("/browsers/{id}/fs/upload_zip", id=id),
+ path_template("/browsers/{id_or_name}/fs/upload_zip", id_or_name=id_or_name),
body=maybe_transform(body, f_upload_zip_params.FUploadZipParams),
files=files,
options=make_request_options(
@@ -579,7 +585,7 @@ def upload_zip(
def write_file(
self,
- id: str,
+ id_or_name: str,
contents: FileContent | BinaryTypes,
*,
path: str,
@@ -607,12 +613,12 @@ def write_file(
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 {})}
extra_headers["Content-Type"] = "application/octet-stream"
return self._put(
- path_template("/browsers/{id}/fs/write_file", id=id),
+ path_template("/browsers/{id_or_name}/fs/write_file", id_or_name=id_or_name),
content=read_file_content(contents) if isinstance(contents, os.PathLike) else contents,
options=make_request_options(
extra_headers=extra_headers,
@@ -660,7 +666,7 @@ def with_streaming_response(self) -> AsyncFsResourceWithStreamingResponse:
async def create_directory(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
mode: str | Omit = omit,
@@ -687,11 +693,11 @@ async def create_directory(
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._put(
- path_template("/browsers/{id}/fs/create_directory", id=id),
+ path_template("/browsers/{id_or_name}/fs/create_directory", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"path": path,
@@ -707,7 +713,7 @@ async def create_directory(
async def delete_directory(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -731,11 +737,11 @@ async def delete_directory(
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._put(
- path_template("/browsers/{id}/fs/delete_directory", id=id),
+ path_template("/browsers/{id_or_name}/fs/delete_directory", id_or_name=id_or_name),
body=await async_maybe_transform({"path": path}, f_delete_directory_params.FDeleteDirectoryParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -745,7 +751,7 @@ async def delete_directory(
async def delete_file(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -769,11 +775,11 @@ async def delete_file(
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._put(
- path_template("/browsers/{id}/fs/delete_file", id=id),
+ path_template("/browsers/{id_or_name}/fs/delete_file", id_or_name=id_or_name),
body=await async_maybe_transform({"path": path}, f_delete_file_params.FDeleteFileParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -783,7 +789,7 @@ async def delete_file(
async def download_dir_zip(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -807,11 +813,11 @@ async def download_dir_zip(
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": "application/zip", **(extra_headers or {})}
return await self._get(
- path_template("/browsers/{id}/fs/download_dir_zip", id=id),
+ path_template("/browsers/{id_or_name}/fs/download_dir_zip", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -824,7 +830,7 @@ async def download_dir_zip(
async def file_info(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -848,10 +854,10 @@ async def file_info(
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("/browsers/{id}/fs/file_info", id=id),
+ path_template("/browsers/{id_or_name}/fs/file_info", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -864,7 +870,7 @@ async def file_info(
async def list_files(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -888,10 +894,10 @@ async def list_files(
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("/browsers/{id}/fs/list_files", id=id),
+ path_template("/browsers/{id_or_name}/fs/list_files", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -904,7 +910,7 @@ async def list_files(
async def move(
self,
- id: str,
+ id_or_name: str,
*,
dest_path: str,
src_path: str,
@@ -931,11 +937,11 @@ async def move(
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._put(
- path_template("/browsers/{id}/fs/move", id=id),
+ path_template("/browsers/{id_or_name}/fs/move", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"dest_path": dest_path,
@@ -951,7 +957,7 @@ async def move(
async def read_file(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -975,11 +981,11 @@ async def read_file(
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": "application/octet-stream", **(extra_headers or {})}
return await self._get(
- path_template("/browsers/{id}/fs/read_file", id=id),
+ path_template("/browsers/{id_or_name}/fs/read_file", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -992,7 +998,7 @@ async def read_file(
async def set_file_permissions(
self,
- id: str,
+ id_or_name: str,
*,
mode: str,
path: str,
@@ -1025,11 +1031,11 @@ async def set_file_permissions(
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._put(
- path_template("/browsers/{id}/fs/set_file_permissions", id=id),
+ path_template("/browsers/{id_or_name}/fs/set_file_permissions", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"mode": mode,
@@ -1047,7 +1053,7 @@ async def set_file_permissions(
async def upload(
self,
- id: str,
+ id_or_name: str,
*,
files: Iterable[f_upload_params.File],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -1069,18 +1075,23 @@ async def upload(
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 {})}
- body = deepcopy_with_paths({"files": files}, [["files", "", "file"]])
- extracted_files = extract_files(cast(Mapping[str, object], body), paths=[["files", "", "file"]])
+ body = deepcopy_with_paths({"files": list(files)}, [["files", "", "file"]])
+ # The remote filesystem pairs each file part with the sibling fields of the
+ # same array entry, so both halves of the form use indexed names
+ # (`files[0][file]`, `files[0][dest_path]`).
+ extracted_files = extract_files(
+ cast(Mapping[str, object], body), paths=[["files", "", "file"]], array_format="indices"
+ )
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
extra_headers["Content-Type"] = "multipart/form-data"
return await self._post(
- path_template("/browsers/{id}/fs/upload", id=id),
- body=await async_maybe_transform(body, f_upload_params.FUploadParams),
+ path_template("/browsers/{id_or_name}/fs/upload", id_or_name=id_or_name),
+ body=indexed_multipart_body(await async_maybe_transform(body, f_upload_params.FUploadParams)),
files=extracted_files,
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -1090,7 +1101,7 @@ async def upload(
async def upload_zip(
self,
- id: str,
+ id_or_name: str,
*,
dest_path: str,
zip_file: FileTypes,
@@ -1115,8 +1126,8 @@ async def upload_zip(
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 {})}
body = deepcopy_with_paths(
{
@@ -1131,7 +1142,7 @@ async def upload_zip(
# multipart/form-data; boundary=---abc--
extra_headers["Content-Type"] = "multipart/form-data"
return await self._post(
- path_template("/browsers/{id}/fs/upload_zip", id=id),
+ path_template("/browsers/{id_or_name}/fs/upload_zip", id_or_name=id_or_name),
body=await async_maybe_transform(body, f_upload_zip_params.FUploadZipParams),
files=files,
options=make_request_options(
@@ -1142,7 +1153,7 @@ async def upload_zip(
async def write_file(
self,
- id: str,
+ id_or_name: str,
contents: FileContent | AsyncBinaryTypes,
*,
path: str,
@@ -1170,12 +1181,12 @@ async def write_file(
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 {})}
extra_headers["Content-Type"] = "application/octet-stream"
return await self._put(
- path_template("/browsers/{id}/fs/write_file", id=id),
+ path_template("/browsers/{id_or_name}/fs/write_file", id_or_name=id_or_name),
content=await async_read_file_content(contents) if isinstance(contents, os.PathLike) else contents,
options=make_request_options(
extra_headers=extra_headers,
diff --git a/src/kernel/resources/browsers/fs/watch.py b/src/kernel/resources/browsers/fs/watch.py
index bc046053..06ba75ef 100644
--- a/src/kernel/resources/browsers/fs/watch.py
+++ b/src/kernel/resources/browsers/fs/watch.py
@@ -49,7 +49,7 @@ def events(
self,
watch_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -69,13 +69,15 @@ def events(
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}")
if not watch_id:
raise ValueError(f"Expected a non-empty value for `watch_id` but received {watch_id!r}")
extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})}
return self._get(
- path_template("/browsers/{id}/fs/watch/{watch_id}/events", id=id, watch_id=watch_id),
+ path_template(
+ "/browsers/{id_or_name}/fs/watch/{watch_id}/events", id_or_name=id_or_name, watch_id=watch_id
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -86,7 +88,7 @@ def events(
def start(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
recursive: bool | Omit = omit,
@@ -113,10 +115,10 @@ def start(
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("/browsers/{id}/fs/watch", id=id),
+ path_template("/browsers/{id_or_name}/fs/watch", id_or_name=id_or_name),
body=maybe_transform(
{
"path": path,
@@ -134,7 +136,7 @@ def stop(
self,
watch_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -154,13 +156,13 @@ def stop(
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}")
if not watch_id:
raise ValueError(f"Expected a non-empty value for `watch_id` but received {watch_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
return self._delete(
- path_template("/browsers/{id}/fs/watch/{watch_id}", id=id, watch_id=watch_id),
+ path_template("/browsers/{id_or_name}/fs/watch/{watch_id}", id_or_name=id_or_name, watch_id=watch_id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -194,7 +196,7 @@ async def events(
self,
watch_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -214,13 +216,15 @@ async def events(
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}")
if not watch_id:
raise ValueError(f"Expected a non-empty value for `watch_id` but received {watch_id!r}")
extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})}
return await self._get(
- path_template("/browsers/{id}/fs/watch/{watch_id}/events", id=id, watch_id=watch_id),
+ path_template(
+ "/browsers/{id_or_name}/fs/watch/{watch_id}/events", id_or_name=id_or_name, watch_id=watch_id
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -231,7 +235,7 @@ async def events(
async def start(
self,
- id: str,
+ id_or_name: str,
*,
path: str,
recursive: bool | Omit = omit,
@@ -258,10 +262,10 @@ async def start(
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("/browsers/{id}/fs/watch", id=id),
+ path_template("/browsers/{id_or_name}/fs/watch", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"path": path,
@@ -279,7 +283,7 @@ async def stop(
self,
watch_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -299,13 +303,13 @@ async def stop(
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}")
if not watch_id:
raise ValueError(f"Expected a non-empty value for `watch_id` but received {watch_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
return await self._delete(
- path_template("/browsers/{id}/fs/watch/{watch_id}", id=id, watch_id=watch_id),
+ path_template("/browsers/{id_or_name}/fs/watch/{watch_id}", id_or_name=id_or_name, watch_id=watch_id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
diff --git a/src/kernel/resources/browsers/logs.py b/src/kernel/resources/browsers/logs.py
index 35ee66dc..db65f29b 100644
--- a/src/kernel/resources/browsers/logs.py
+++ b/src/kernel/resources/browsers/logs.py
@@ -48,7 +48,7 @@ def with_streaming_response(self) -> LogsResourceWithStreamingResponse:
def stream(
self,
- id: str,
+ id_or_name: str,
*,
source: Literal["path", "supervisor"],
follow: bool | Omit = omit,
@@ -77,11 +77,11 @@ def stream(
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": "text/event-stream", **(extra_headers or {})}
return self._get(
- path_template("/browsers/{id}/logs/stream", id=id),
+ path_template("/browsers/{id_or_name}/logs/stream", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -127,7 +127,7 @@ def with_streaming_response(self) -> AsyncLogsResourceWithStreamingResponse:
async def stream(
self,
- id: str,
+ id_or_name: str,
*,
source: Literal["path", "supervisor"],
follow: bool | Omit = omit,
@@ -156,11 +156,11 @@ async def stream(
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": "text/event-stream", **(extra_headers or {})}
return await self._get(
- path_template("/browsers/{id}/logs/stream", id=id),
+ path_template("/browsers/{id_or_name}/logs/stream", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
diff --git a/src/kernel/resources/browsers/playwright.py b/src/kernel/resources/browsers/playwright.py
deleted file mode 100644
index 8d261ed5..00000000
--- a/src/kernel/resources/browsers/playwright.py
+++ /dev/null
@@ -1,209 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-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.browsers import playwright_execute_params
-from ...types.browsers.playwright_execute_response import PlaywrightExecuteResponse
-
-__all__ = ["PlaywrightResource", "AsyncPlaywrightResource"]
-
-
-class PlaywrightResource(SyncAPIResource):
- """Execute Playwright code against the browser instance."""
-
- @cached_property
- def with_raw_response(self) -> PlaywrightResourceWithRawResponse:
- """
- 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 PlaywrightResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> PlaywrightResourceWithStreamingResponse:
- """
- 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 PlaywrightResourceWithStreamingResponse(self)
-
- def execute(
- self,
- id: str,
- *,
- code: str,
- timeout_sec: 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,
- ) -> PlaywrightExecuteResponse:
- """
- Execute arbitrary Playwright code in a fresh execution context against the
- browser. The code runs in the same VM as the browser, minimizing latency and
- maximizing throughput. It has access to 'page', 'context', and 'browser'
- variables. It can `return` a value, and this value is returned in the response.
-
- Args:
- code: TypeScript/JavaScript code to execute. The code has access to 'page', 'context',
- and 'browser' variables. It runs within a function, so you can use a return
- statement at the end to return a value. This value is returned as the `result`
- property in the response. Example: "await page.goto('https://example.com');
- return await page.title();"
-
- timeout_sec: Maximum execution time in seconds. Default is 60.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return self._post(
- path_template("/browsers/{id}/playwright/execute", id=id),
- body=maybe_transform(
- {
- "code": code,
- "timeout_sec": timeout_sec,
- },
- playwright_execute_params.PlaywrightExecuteParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=PlaywrightExecuteResponse,
- )
-
-
-class AsyncPlaywrightResource(AsyncAPIResource):
- """Execute Playwright code against the browser instance."""
-
- @cached_property
- def with_raw_response(self) -> AsyncPlaywrightResourceWithRawResponse:
- """
- 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 AsyncPlaywrightResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncPlaywrightResourceWithStreamingResponse:
- """
- 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 AsyncPlaywrightResourceWithStreamingResponse(self)
-
- async def execute(
- self,
- id: str,
- *,
- code: str,
- timeout_sec: 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,
- ) -> PlaywrightExecuteResponse:
- """
- Execute arbitrary Playwright code in a fresh execution context against the
- browser. The code runs in the same VM as the browser, minimizing latency and
- maximizing throughput. It has access to 'page', 'context', and 'browser'
- variables. It can `return` a value, and this value is returned in the response.
-
- Args:
- code: TypeScript/JavaScript code to execute. The code has access to 'page', 'context',
- and 'browser' variables. It runs within a function, so you can use a return
- statement at the end to return a value. This value is returned as the `result`
- property in the response. Example: "await page.goto('https://example.com');
- return await page.title();"
-
- timeout_sec: Maximum execution time in seconds. Default is 60.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- if not id:
- raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
- return await self._post(
- path_template("/browsers/{id}/playwright/execute", id=id),
- body=await async_maybe_transform(
- {
- "code": code,
- "timeout_sec": timeout_sec,
- },
- playwright_execute_params.PlaywrightExecuteParams,
- ),
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=PlaywrightExecuteResponse,
- )
-
-
-class PlaywrightResourceWithRawResponse:
- def __init__(self, playwright: PlaywrightResource) -> None:
- self._playwright = playwright
-
- self.execute = to_raw_response_wrapper(
- playwright.execute,
- )
-
-
-class AsyncPlaywrightResourceWithRawResponse:
- def __init__(self, playwright: AsyncPlaywrightResource) -> None:
- self._playwright = playwright
-
- self.execute = async_to_raw_response_wrapper(
- playwright.execute,
- )
-
-
-class PlaywrightResourceWithStreamingResponse:
- def __init__(self, playwright: PlaywrightResource) -> None:
- self._playwright = playwright
-
- self.execute = to_streamed_response_wrapper(
- playwright.execute,
- )
-
-
-class AsyncPlaywrightResourceWithStreamingResponse:
- def __init__(self, playwright: AsyncPlaywrightResource) -> None:
- self._playwright = playwright
-
- self.execute = async_to_streamed_response_wrapper(
- playwright.execute,
- )
diff --git a/src/kernel/resources/browsers/playwright/__init__.py b/src/kernel/resources/browsers/playwright/__init__.py
new file mode 100644
index 00000000..fa98144e
--- /dev/null
+++ b/src/kernel/resources/browsers/playwright/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .executors import (
+ ExecutorsResource,
+ AsyncExecutorsResource,
+ ExecutorsResourceWithRawResponse,
+ AsyncExecutorsResourceWithRawResponse,
+ ExecutorsResourceWithStreamingResponse,
+ AsyncExecutorsResourceWithStreamingResponse,
+)
+from .playwright import (
+ PlaywrightResource,
+ AsyncPlaywrightResource,
+ PlaywrightResourceWithRawResponse,
+ AsyncPlaywrightResourceWithRawResponse,
+ PlaywrightResourceWithStreamingResponse,
+ AsyncPlaywrightResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ExecutorsResource",
+ "AsyncExecutorsResource",
+ "ExecutorsResourceWithRawResponse",
+ "AsyncExecutorsResourceWithRawResponse",
+ "ExecutorsResourceWithStreamingResponse",
+ "AsyncExecutorsResourceWithStreamingResponse",
+ "PlaywrightResource",
+ "AsyncPlaywrightResource",
+ "PlaywrightResourceWithRawResponse",
+ "AsyncPlaywrightResourceWithRawResponse",
+ "PlaywrightResourceWithStreamingResponse",
+ "AsyncPlaywrightResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/browsers/playwright/executors.py b/src/kernel/resources/browsers/playwright/executors.py
new file mode 100644
index 00000000..11d78f10
--- /dev/null
+++ b/src/kernel/resources/browsers/playwright/executors.py
@@ -0,0 +1,307 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+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 ...._base_client import make_request_options
+from ....types.browsers.playwright import executor_delete_params
+from ....types.browsers.playwright.executor_list import ExecutorList
+
+__all__ = ["ExecutorsResource", "AsyncExecutorsResource"]
+
+
+class ExecutorsResource(SyncAPIResource):
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> ExecutorsResourceWithRawResponse:
+ """
+ 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 ExecutorsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ExecutorsResourceWithStreamingResponse:
+ """
+ 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 ExecutorsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ExecutorList:
+ """
+ Lists the default executor first, then the named executors created by POST
+ /browsers/{id_or_name}/playwright/execute. Each entry reports whether a call is
+ running on it and, for named executors, the target ID and URL of the tab it
+ owns. Returns 404 for a browser whose image predates executors.
+
+ 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_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("/browsers/{id_or_name}/playwright/executors", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ExecutorList,
+ )
+
+ def delete(
+ self,
+ name: str,
+ *,
+ id_or_name: str,
+ close_tab: bool | 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,
+ ) -> None:
+ """Stops the executor's process.
+
+ A call running on it fails with an error saying
+ the executor was deleted. By default the executor's tab is closed too. The name
+ can be reused; the next call with it creates a new executor and tab.
+
+ The default executor is restarted instead of removed: its process is stopped, a
+ call running on it fails with an error saying the executor was restarted, and
+ queued and later calls run on a new process. It owns no tab, so 'close_tab' has
+ no effect on it.
+
+ Args:
+ name: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
+ close_tab: Close the executor's tab. Defaults to true.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not name:
+ raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/browsers/{id_or_name}/playwright/executors/{name}", id_or_name=id_or_name, name=name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"close_tab": close_tab}, executor_delete_params.ExecutorDeleteParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncExecutorsResource(AsyncAPIResource):
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncExecutorsResourceWithRawResponse:
+ """
+ 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 AsyncExecutorsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncExecutorsResourceWithStreamingResponse:
+ """
+ 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 AsyncExecutorsResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ExecutorList:
+ """
+ Lists the default executor first, then the named executors created by POST
+ /browsers/{id_or_name}/playwright/execute. Each entry reports whether a call is
+ running on it and, for named executors, the target ID and URL of the tab it
+ owns. Returns 404 for a browser whose image predates executors.
+
+ 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_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("/browsers/{id_or_name}/playwright/executors", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ExecutorList,
+ )
+
+ async def delete(
+ self,
+ name: str,
+ *,
+ id_or_name: str,
+ close_tab: bool | 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,
+ ) -> None:
+ """Stops the executor's process.
+
+ A call running on it fails with an error saying
+ the executor was deleted. By default the executor's tab is closed too. The name
+ can be reused; the next call with it creates a new executor and tab.
+
+ The default executor is restarted instead of removed: its process is stopped, a
+ call running on it fails with an error saying the executor was restarted, and
+ queued and later calls run on a new process. It owns no tab, so 'close_tab' has
+ no effect on it.
+
+ Args:
+ name: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
+ close_tab: Close the executor's tab. Defaults to true.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not name:
+ raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/browsers/{id_or_name}/playwright/executors/{name}", id_or_name=id_or_name, name=name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"close_tab": close_tab}, executor_delete_params.ExecutorDeleteParams
+ ),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class ExecutorsResourceWithRawResponse:
+ def __init__(self, executors: ExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = to_raw_response_wrapper(
+ executors.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ executors.delete,
+ )
+
+
+class AsyncExecutorsResourceWithRawResponse:
+ def __init__(self, executors: AsyncExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = async_to_raw_response_wrapper(
+ executors.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ executors.delete,
+ )
+
+
+class ExecutorsResourceWithStreamingResponse:
+ def __init__(self, executors: ExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = to_streamed_response_wrapper(
+ executors.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ executors.delete,
+ )
+
+
+class AsyncExecutorsResourceWithStreamingResponse:
+ def __init__(self, executors: AsyncExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = async_to_streamed_response_wrapper(
+ executors.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ executors.delete,
+ )
diff --git a/src/kernel/resources/browsers/playwright/playwright.py b/src/kernel/resources/browsers/playwright/playwright.py
new file mode 100644
index 00000000..00f5f10e
--- /dev/null
+++ b/src/kernel/resources/browsers/playwright/playwright.py
@@ -0,0 +1,345 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from .executors import (
+ ExecutorsResource,
+ AsyncExecutorsResource,
+ ExecutorsResourceWithRawResponse,
+ AsyncExecutorsResourceWithRawResponse,
+ ExecutorsResourceWithStreamingResponse,
+ AsyncExecutorsResourceWithStreamingResponse,
+)
+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.browsers import playwright_execute_params
+from ....types.browsers.playwright_execute_response import PlaywrightExecuteResponse
+
+__all__ = ["PlaywrightResource", "AsyncPlaywrightResource"]
+
+
+class PlaywrightResource(SyncAPIResource):
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def executors(self) -> ExecutorsResource:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return ExecutorsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> PlaywrightResourceWithRawResponse:
+ """
+ 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 PlaywrightResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PlaywrightResourceWithStreamingResponse:
+ """
+ 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 PlaywrightResourceWithStreamingResponse(self)
+
+ def execute(
+ self,
+ id_or_name: str,
+ *,
+ code: str,
+ executor: str | Omit = omit,
+ timeout_sec: 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,
+ ) -> PlaywrightExecuteResponse:
+ """
+ Execute arbitrary Playwright code in a fresh execution context against the
+ browser. The code runs in the same VM as the browser, minimizing latency and
+ maximizing throughput. It has access to 'page', 'context', 'browser', and
+ 'webmcp' variables. Use 'webmcp.listTools()' to discover browser-wide WebMCP
+ tools and 'webmcp.invokeTool(toolRef, input?, { timeoutSec? })' to invoke an
+ exact registration. It can `return` a value, and this value is returned in the
+ response.
+
+ Every call runs in an executor: a dedicated Node.js process with its own browser
+ connection. Calls on different executors run concurrently; calls on the same
+ executor run one at a time. A timeout, crash, or blocked event loop in one
+ executor does not affect other executors. After a timeout the executor keeps its
+ process and drops its browser connection, so code abandoned by the timeout
+ cannot keep driving the browser. After a crash or a blocked event loop, the next
+ call on that executor starts a fresh process.
+
+ Calls without 'executor' run in the executor named 'default', which always
+ exists and is the same as passing 'executor: "default"'. In the default
+ executor, 'page' is bound to an active tab reported by Chrome. In single-window
+ sessions, this is the foreground tab. When multiple browser windows are open,
+ Chrome reports one active tab per window and the selected window is unspecified.
+ 'context' is the BrowserContext that owns the selected page. Use
+ 'browser.contexts()' to select a context or page explicitly.
+
+ Pass any other name to run the call in a named executor. The first call with a
+ new name creates it. Each named executor owns a tab: its first call opens a new
+ background tab in the default browser context, and 'page' is bound to that tab
+ on every later call while it stays open. Opening it does not change the active
+ tab of an existing window. If the tab is closed, the next call opens a new one
+ and reports 'tab.created: true'. Executor code can still reach other tabs
+ through 'context' and 'browser'; ownership only decides what 'page' is bound to.
+ Use named executors to drive several tabs of one browser in parallel.
+
+ A browser can have at most 8 named executors; the default executor does not
+ count. A call that would create another returns 409 with the current executors;
+ delete one with DELETE /browsers/{id_or_name}/playwright/executors/{name}. Named
+ executors are not removed automatically while the browser runs; when it shuts
+ down, they are removed and their tabs closed.
+
+ Args:
+ code: TypeScript/JavaScript code to execute. The code has access to 'page', 'context',
+ and 'browser' variables. It runs within a function, so you can use a return
+ statement at the end to return a value. This value is returned as the `result`
+ property in the response. Example: "await page.goto('https://example.com');
+ return await page.title();"
+
+ executor: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
+ timeout_sec: Maximum execution time in seconds. Default is 60.
+
+ 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_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("/browsers/{id_or_name}/playwright/execute", id_or_name=id_or_name),
+ body=maybe_transform(
+ {
+ "code": code,
+ "executor": executor,
+ "timeout_sec": timeout_sec,
+ },
+ playwright_execute_params.PlaywrightExecuteParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PlaywrightExecuteResponse,
+ )
+
+
+class AsyncPlaywrightResource(AsyncAPIResource):
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def executors(self) -> AsyncExecutorsResource:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return AsyncExecutorsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPlaywrightResourceWithRawResponse:
+ """
+ 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 AsyncPlaywrightResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPlaywrightResourceWithStreamingResponse:
+ """
+ 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 AsyncPlaywrightResourceWithStreamingResponse(self)
+
+ async def execute(
+ self,
+ id_or_name: str,
+ *,
+ code: str,
+ executor: str | Omit = omit,
+ timeout_sec: 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,
+ ) -> PlaywrightExecuteResponse:
+ """
+ Execute arbitrary Playwright code in a fresh execution context against the
+ browser. The code runs in the same VM as the browser, minimizing latency and
+ maximizing throughput. It has access to 'page', 'context', 'browser', and
+ 'webmcp' variables. Use 'webmcp.listTools()' to discover browser-wide WebMCP
+ tools and 'webmcp.invokeTool(toolRef, input?, { timeoutSec? })' to invoke an
+ exact registration. It can `return` a value, and this value is returned in the
+ response.
+
+ Every call runs in an executor: a dedicated Node.js process with its own browser
+ connection. Calls on different executors run concurrently; calls on the same
+ executor run one at a time. A timeout, crash, or blocked event loop in one
+ executor does not affect other executors. After a timeout the executor keeps its
+ process and drops its browser connection, so code abandoned by the timeout
+ cannot keep driving the browser. After a crash or a blocked event loop, the next
+ call on that executor starts a fresh process.
+
+ Calls without 'executor' run in the executor named 'default', which always
+ exists and is the same as passing 'executor: "default"'. In the default
+ executor, 'page' is bound to an active tab reported by Chrome. In single-window
+ sessions, this is the foreground tab. When multiple browser windows are open,
+ Chrome reports one active tab per window and the selected window is unspecified.
+ 'context' is the BrowserContext that owns the selected page. Use
+ 'browser.contexts()' to select a context or page explicitly.
+
+ Pass any other name to run the call in a named executor. The first call with a
+ new name creates it. Each named executor owns a tab: its first call opens a new
+ background tab in the default browser context, and 'page' is bound to that tab
+ on every later call while it stays open. Opening it does not change the active
+ tab of an existing window. If the tab is closed, the next call opens a new one
+ and reports 'tab.created: true'. Executor code can still reach other tabs
+ through 'context' and 'browser'; ownership only decides what 'page' is bound to.
+ Use named executors to drive several tabs of one browser in parallel.
+
+ A browser can have at most 8 named executors; the default executor does not
+ count. A call that would create another returns 409 with the current executors;
+ delete one with DELETE /browsers/{id_or_name}/playwright/executors/{name}. Named
+ executors are not removed automatically while the browser runs; when it shuts
+ down, they are removed and their tabs closed.
+
+ Args:
+ code: TypeScript/JavaScript code to execute. The code has access to 'page', 'context',
+ and 'browser' variables. It runs within a function, so you can use a return
+ statement at the end to return a value. This value is returned as the `result`
+ property in the response. Example: "await page.goto('https://example.com');
+ return await page.title();"
+
+ executor: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
+ timeout_sec: Maximum execution time in seconds. Default is 60.
+
+ 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_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("/browsers/{id_or_name}/playwright/execute", id_or_name=id_or_name),
+ body=await async_maybe_transform(
+ {
+ "code": code,
+ "executor": executor,
+ "timeout_sec": timeout_sec,
+ },
+ playwright_execute_params.PlaywrightExecuteParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PlaywrightExecuteResponse,
+ )
+
+
+class PlaywrightResourceWithRawResponse:
+ def __init__(self, playwright: PlaywrightResource) -> None:
+ self._playwright = playwright
+
+ self.execute = to_raw_response_wrapper(
+ playwright.execute,
+ )
+
+ @cached_property
+ def executors(self) -> ExecutorsResourceWithRawResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return ExecutorsResourceWithRawResponse(self._playwright.executors)
+
+
+class AsyncPlaywrightResourceWithRawResponse:
+ def __init__(self, playwright: AsyncPlaywrightResource) -> None:
+ self._playwright = playwright
+
+ self.execute = async_to_raw_response_wrapper(
+ playwright.execute,
+ )
+
+ @cached_property
+ def executors(self) -> AsyncExecutorsResourceWithRawResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return AsyncExecutorsResourceWithRawResponse(self._playwright.executors)
+
+
+class PlaywrightResourceWithStreamingResponse:
+ def __init__(self, playwright: PlaywrightResource) -> None:
+ self._playwright = playwright
+
+ self.execute = to_streamed_response_wrapper(
+ playwright.execute,
+ )
+
+ @cached_property
+ def executors(self) -> ExecutorsResourceWithStreamingResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return ExecutorsResourceWithStreamingResponse(self._playwright.executors)
+
+
+class AsyncPlaywrightResourceWithStreamingResponse:
+ def __init__(self, playwright: AsyncPlaywrightResource) -> None:
+ self._playwright = playwright
+
+ self.execute = async_to_streamed_response_wrapper(
+ playwright.execute,
+ )
+
+ @cached_property
+ def executors(self) -> AsyncExecutorsResourceWithStreamingResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return AsyncExecutorsResourceWithStreamingResponse(self._playwright.executors)
diff --git a/src/kernel/resources/browsers/process.py b/src/kernel/resources/browsers/process.py
index 83827d38..c792148d 100644
--- a/src/kernel/resources/browsers/process.py
+++ b/src/kernel/resources/browsers/process.py
@@ -61,7 +61,7 @@ def with_streaming_response(self) -> ProcessResourceWithStreamingResponse:
def exec(
self,
- id: str,
+ id_or_name: str,
*,
command: str,
args: SequenceNotStr[str] | Omit = omit,
@@ -103,10 +103,10 @@ def exec(
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("/browsers/{id}/process/exec", id=id),
+ path_template("/browsers/{id_or_name}/process/exec", id_or_name=id_or_name),
body=maybe_transform(
{
"command": command,
@@ -129,7 +129,7 @@ def kill(
self,
process_id: str,
*,
- id: str,
+ id_or_name: str,
signal: Literal["TERM", "KILL", "INT", "HUP"],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -152,12 +152,14 @@ def kill(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return self._post(
- path_template("/browsers/{id}/process/{process_id}/kill", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/kill", id_or_name=id_or_name, process_id=process_id
+ ),
body=maybe_transform({"signal": signal}, process_kill_params.ProcessKillParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -169,7 +171,7 @@ def resize(
self,
process_id: str,
*,
- id: str,
+ id_or_name: str,
cols: int,
rows: int,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -195,12 +197,14 @@ def resize(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return self._post(
- path_template("/browsers/{id}/process/{process_id}/resize", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/resize", id_or_name=id_or_name, process_id=process_id
+ ),
body=maybe_transform(
{
"cols": cols,
@@ -216,7 +220,7 @@ def resize(
def spawn(
self,
- id: str,
+ id_or_name: str,
*,
command: str,
allocate_tty: bool | Omit = omit,
@@ -267,10 +271,10 @@ def spawn(
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("/browsers/{id}/process/spawn", id=id),
+ path_template("/browsers/{id_or_name}/process/spawn", id_or_name=id_or_name),
body=maybe_transform(
{
"command": command,
@@ -296,7 +300,7 @@ def status(
self,
process_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -316,12 +320,14 @@ def status(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return self._get(
- path_template("/browsers/{id}/process/{process_id}/status", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/status", id_or_name=id_or_name, process_id=process_id
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -332,7 +338,7 @@ def stdin(
self,
process_id: str,
*,
- id: str,
+ id_or_name: str,
data_b64: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -355,12 +361,14 @@ def stdin(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return self._post(
- path_template("/browsers/{id}/process/{process_id}/stdin", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/stdin", id_or_name=id_or_name, process_id=process_id
+ ),
body=maybe_transform({"data_b64": data_b64}, process_stdin_params.ProcessStdinParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -372,7 +380,7 @@ def stdout_stream(
self,
process_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -392,13 +400,17 @@ def stdout_stream(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})}
return self._get(
- path_template("/browsers/{id}/process/{process_id}/stdout/stream", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/stdout/stream",
+ id_or_name=id_or_name,
+ process_id=process_id,
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -432,7 +444,7 @@ def with_streaming_response(self) -> AsyncProcessResourceWithStreamingResponse:
async def exec(
self,
- id: str,
+ id_or_name: str,
*,
command: str,
args: SequenceNotStr[str] | Omit = omit,
@@ -474,10 +486,10 @@ async def exec(
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("/browsers/{id}/process/exec", id=id),
+ path_template("/browsers/{id_or_name}/process/exec", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"command": command,
@@ -500,7 +512,7 @@ async def kill(
self,
process_id: str,
*,
- id: str,
+ id_or_name: str,
signal: Literal["TERM", "KILL", "INT", "HUP"],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -523,12 +535,14 @@ async def kill(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return await self._post(
- path_template("/browsers/{id}/process/{process_id}/kill", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/kill", id_or_name=id_or_name, process_id=process_id
+ ),
body=await async_maybe_transform({"signal": signal}, process_kill_params.ProcessKillParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -540,7 +554,7 @@ async def resize(
self,
process_id: str,
*,
- id: str,
+ id_or_name: str,
cols: int,
rows: int,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -566,12 +580,14 @@ async def resize(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return await self._post(
- path_template("/browsers/{id}/process/{process_id}/resize", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/resize", id_or_name=id_or_name, process_id=process_id
+ ),
body=await async_maybe_transform(
{
"cols": cols,
@@ -587,7 +603,7 @@ async def resize(
async def spawn(
self,
- id: str,
+ id_or_name: str,
*,
command: str,
allocate_tty: bool | Omit = omit,
@@ -638,10 +654,10 @@ async def spawn(
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("/browsers/{id}/process/spawn", id=id),
+ path_template("/browsers/{id_or_name}/process/spawn", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"command": command,
@@ -667,7 +683,7 @@ async def status(
self,
process_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -687,12 +703,14 @@ async def status(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return await self._get(
- path_template("/browsers/{id}/process/{process_id}/status", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/status", id_or_name=id_or_name, process_id=process_id
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -703,7 +721,7 @@ async def stdin(
self,
process_id: str,
*,
- id: str,
+ id_or_name: str,
data_b64: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -726,12 +744,14 @@ async def stdin(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
return await self._post(
- path_template("/browsers/{id}/process/{process_id}/stdin", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/stdin", id_or_name=id_or_name, process_id=process_id
+ ),
body=await async_maybe_transform({"data_b64": data_b64}, process_stdin_params.ProcessStdinParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
@@ -743,7 +763,7 @@ async def stdout_stream(
self,
process_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -763,13 +783,17 @@ async def stdout_stream(
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}")
if not process_id:
raise ValueError(f"Expected a non-empty value for `process_id` but received {process_id!r}")
extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})}
return await self._get(
- path_template("/browsers/{id}/process/{process_id}/stdout/stream", id=id, process_id=process_id),
+ path_template(
+ "/browsers/{id_or_name}/process/{process_id}/stdout/stream",
+ id_or_name=id_or_name,
+ process_id=process_id,
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
diff --git a/src/kernel/resources/browsers/replays.py b/src/kernel/resources/browsers/replays.py
index 8f56ea5f..d74ce81c 100644
--- a/src/kernel/resources/browsers/replays.py
+++ b/src/kernel/resources/browsers/replays.py
@@ -54,7 +54,7 @@ def with_streaming_response(self) -> ReplaysResourceWithStreamingResponse:
def list(
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.
@@ -75,10 +75,10 @@ def list(
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("/browsers/{id}/replays", id=id),
+ path_template("/browsers/{id_or_name}/replays", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -89,7 +89,7 @@ def download(
self,
replay_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -109,13 +109,13 @@ def download(
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}")
if not replay_id:
raise ValueError(f"Expected a non-empty value for `replay_id` but received {replay_id!r}")
extra_headers = {"Accept": "video/mp4", **(extra_headers or {})}
return self._get(
- path_template("/browsers/{id}/replays/{replay_id}", id=id, replay_id=replay_id),
+ path_template("/browsers/{id_or_name}/replays/{replay_id}", id_or_name=id_or_name, replay_id=replay_id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -124,10 +124,11 @@ def download(
def start(
self,
- id: str,
+ id_or_name: str,
*,
framerate: int | Omit = omit,
max_duration_in_seconds: int | Omit = omit,
+ record_audio: bool | 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,
@@ -144,6 +145,9 @@ def start(
max_duration_in_seconds: Maximum recording duration in seconds.
+ record_audio: Record audio in addition to video. When false (the default), the recording is
+ video-only.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -152,14 +156,15 @@ def start(
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("/browsers/{id}/replays", id=id),
+ path_template("/browsers/{id_or_name}/replays", id_or_name=id_or_name),
body=maybe_transform(
{
"framerate": framerate,
"max_duration_in_seconds": max_duration_in_seconds,
+ "record_audio": record_audio,
},
replay_start_params.ReplayStartParams,
),
@@ -173,7 +178,7 @@ def stop(
self,
replay_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -193,13 +198,15 @@ def stop(
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}")
if not replay_id:
raise ValueError(f"Expected a non-empty value for `replay_id` but received {replay_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
return self._post(
- path_template("/browsers/{id}/replays/{replay_id}/stop", id=id, replay_id=replay_id),
+ path_template(
+ "/browsers/{id_or_name}/replays/{replay_id}/stop", id_or_name=id_or_name, replay_id=replay_id
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -231,7 +238,7 @@ def with_streaming_response(self) -> AsyncReplaysResourceWithStreamingResponse:
async def list(
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.
@@ -252,10 +259,10 @@ async def list(
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("/browsers/{id}/replays", id=id),
+ path_template("/browsers/{id_or_name}/replays", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -266,7 +273,7 @@ async def download(
self,
replay_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -286,13 +293,13 @@ async def download(
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}")
if not replay_id:
raise ValueError(f"Expected a non-empty value for `replay_id` but received {replay_id!r}")
extra_headers = {"Accept": "video/mp4", **(extra_headers or {})}
return await self._get(
- path_template("/browsers/{id}/replays/{replay_id}", id=id, replay_id=replay_id),
+ path_template("/browsers/{id_or_name}/replays/{replay_id}", id_or_name=id_or_name, replay_id=replay_id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -301,10 +308,11 @@ async def download(
async def start(
self,
- id: str,
+ id_or_name: str,
*,
framerate: int | Omit = omit,
max_duration_in_seconds: int | Omit = omit,
+ record_audio: bool | 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,
@@ -321,6 +329,9 @@ async def start(
max_duration_in_seconds: Maximum recording duration in seconds.
+ record_audio: Record audio in addition to video. When false (the default), the recording is
+ video-only.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -329,14 +340,15 @@ async def start(
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("/browsers/{id}/replays", id=id),
+ path_template("/browsers/{id_or_name}/replays", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"framerate": framerate,
"max_duration_in_seconds": max_duration_in_seconds,
+ "record_audio": record_audio,
},
replay_start_params.ReplayStartParams,
),
@@ -350,7 +362,7 @@ async def stop(
self,
replay_id: str,
*,
- 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.
extra_headers: Headers | None = None,
@@ -370,13 +382,15 @@ async def stop(
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}")
if not replay_id:
raise ValueError(f"Expected a non-empty value for `replay_id` but received {replay_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
return await self._post(
- path_template("/browsers/{id}/replays/{replay_id}/stop", id=id, replay_id=replay_id),
+ path_template(
+ "/browsers/{id_or_name}/replays/{replay_id}/stop", id_or_name=id_or_name, replay_id=replay_id
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
diff --git a/src/kernel/resources/browsers/telemetry.py b/src/kernel/resources/browsers/telemetry.py
new file mode 100644
index 00000000..c3e889b3
--- /dev/null
+++ b/src/kernel/resources/browsers/telemetry.py
@@ -0,0 +1,475 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._streaming import Stream, AsyncStream
+from ...pagination import SyncOffsetPagination, AsyncOffsetPagination
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.browsers import telemetry_events_params, telemetry_stream_params
+from ...types.browsers.telemetry_events_response import TelemetryEventsResponse
+from ...types.browsers.telemetry_stream_response import TelemetryStreamResponse
+
+__all__ = ["TelemetryResource", "AsyncTelemetryResource"]
+
+
+class TelemetryResource(SyncAPIResource):
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> TelemetryResourceWithRawResponse:
+ """
+ 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 TelemetryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> TelemetryResourceWithStreamingResponse:
+ """
+ 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 TelemetryResourceWithStreamingResponse(self)
+
+ def events(
+ self,
+ id_or_name: str,
+ *,
+ category: List[
+ Literal[
+ "console",
+ "network",
+ "page",
+ "interaction",
+ "control",
+ "platform",
+ "connection",
+ "system",
+ "screenshot",
+ "captcha",
+ "monitor",
+ ]
+ ]
+ | Omit = omit,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ order: str | Omit = omit,
+ since: str | Omit = omit,
+ type: SequenceNotStr[str] | Omit = omit,
+ until: str | 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[TelemetryEventsResponse]:
+ """Reads a page of telemetry events for the browser session.
+
+ To page through
+ results, pass the X-Next-Offset value from the previous response as offset and
+ repeat while X-Has-More is true. The category and type filters apply within each
+ page, so a filtered page may be empty while X-Has-More is true. Returns an empty
+ list when telemetry data is unavailable.
+
+ Args:
+ category: Restrict results to these event categories. Repeat the parameter for multiple
+ values.
+
+ limit: Maximum number of events per page. Defaults to 20.
+
+ offset: Opaque pagination cursor: pass the X-Next-Offset value from the previous
+ response to fetch the next page. When set, paging continues from this cursor and
+ since is ignored, while until still bounds the page. It is not an event's seq
+ field, so do not derive it from the response body.
+
+ order: Read direction. asc (default) reads oldest first, starting from since or the
+ offset cursor. desc reads newest first: each request returns one page of up to
+ limit records ending at the offset cursor (or until, or the newest archived
+ event); combining desc with since is rejected with a 400.
+
+ since: Start of the window: an RFC-3339 timestamp, or a duration like 5m meaning that
+ long ago. Defaults to 5m. Ignored when offset is set.
+
+ type: Restrict results to these event types, such as page_crashed or
+ captcha_challenge_result. Repeat the parameter for multiple values. Combines
+ with category: when both are set an event must match both.
+
+ until: End of the window (exclusive): an RFC-3339 timestamp, or a duration like 5m
+ meaning that long ago.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return self._get_api_list(
+ path_template("/browsers/{id_or_name}/telemetry/events", id_or_name=id_or_name),
+ page=SyncOffsetPagination[TelemetryEventsResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "category": category,
+ "limit": limit,
+ "offset": offset,
+ "order": order,
+ "since": since,
+ "type": type,
+ "until": until,
+ },
+ telemetry_events_params.TelemetryEventsParams,
+ ),
+ ),
+ model=TelemetryEventsResponse,
+ )
+
+ def stream(
+ self,
+ id_or_name: str,
+ *,
+ replay: str | Omit = omit,
+ type: SequenceNotStr[str] | Omit = omit,
+ last_event_id: str | 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,
+ ) -> Stream[TelemetryStreamResponse]:
+ """Streams browser telemetry events as a server-sent events (SSE) stream.
+
+ The
+ stream closes when the browser session terminates. Each event frame includes an
+ id: field containing a monotonically increasing sequence number; pass it as
+ Last-Event-ID on reconnect to resume without gaps. The event: field is never
+ set; event frames carry JSON in the data: field. A frame with an id: field and
+ no data: field moves Last-Event-ID past events that were not delivered, because
+ a type filter excluded them or they could not be decoded or exceeded the size
+ limit; SSE clients dispatch no event for it. A keepalive comment frame is sent
+ every 15 seconds when no events arrive. Returns 404 if the browser session does
+ not exist. If telemetry was not enabled on the session, the stream opens but no
+ events are delivered. Fresh connections only see new events; pass replay=all to
+ start from the oldest retained event instead.
+
+ Args:
+ replay: Pass `all` to start from the oldest retained event instead of only new events;
+ any other value is treated as from-now. The buffer is bounded, so the first
+ event id may be greater than 1 if older events were evicted.
+
+ type: Deliver only these event types, such as captcha_solve_started or
+ captcha_challenge_result. Repeat the parameter or pass comma-separated values
+ for multiple types. Keepalive frames are always delivered. Filtered-out events
+ are not sent. An id-only frame carrying the latest skipped id is sent about
+ every 15 seconds while skipped events keep arriving, and otherwise with the next
+ keepalive or when the stream ends, so the connection stays active and
+ Last-Event-ID moves past them; a reconnect resumes after the skipped events.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})}
+ extra_headers = {**strip_not_given({"Last-Event-ID": last_event_id}), **(extra_headers or {})}
+ return self._get(
+ path_template("/browsers/{id_or_name}/telemetry/stream", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "replay": replay,
+ "type": type,
+ },
+ telemetry_stream_params.TelemetryStreamParams,
+ ),
+ ),
+ cast_to=TelemetryStreamResponse,
+ stream=True,
+ stream_cls=Stream[TelemetryStreamResponse],
+ )
+
+
+class AsyncTelemetryResource(AsyncAPIResource):
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncTelemetryResourceWithRawResponse:
+ """
+ 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 AsyncTelemetryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncTelemetryResourceWithStreamingResponse:
+ """
+ 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 AsyncTelemetryResourceWithStreamingResponse(self)
+
+ def events(
+ self,
+ id_or_name: str,
+ *,
+ category: List[
+ Literal[
+ "console",
+ "network",
+ "page",
+ "interaction",
+ "control",
+ "platform",
+ "connection",
+ "system",
+ "screenshot",
+ "captcha",
+ "monitor",
+ ]
+ ]
+ | Omit = omit,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ order: str | Omit = omit,
+ since: str | Omit = omit,
+ type: SequenceNotStr[str] | Omit = omit,
+ until: str | 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[TelemetryEventsResponse, AsyncOffsetPagination[TelemetryEventsResponse]]:
+ """Reads a page of telemetry events for the browser session.
+
+ To page through
+ results, pass the X-Next-Offset value from the previous response as offset and
+ repeat while X-Has-More is true. The category and type filters apply within each
+ page, so a filtered page may be empty while X-Has-More is true. Returns an empty
+ list when telemetry data is unavailable.
+
+ Args:
+ category: Restrict results to these event categories. Repeat the parameter for multiple
+ values.
+
+ limit: Maximum number of events per page. Defaults to 20.
+
+ offset: Opaque pagination cursor: pass the X-Next-Offset value from the previous
+ response to fetch the next page. When set, paging continues from this cursor and
+ since is ignored, while until still bounds the page. It is not an event's seq
+ field, so do not derive it from the response body.
+
+ order: Read direction. asc (default) reads oldest first, starting from since or the
+ offset cursor. desc reads newest first: each request returns one page of up to
+ limit records ending at the offset cursor (or until, or the newest archived
+ event); combining desc with since is rejected with a 400.
+
+ since: Start of the window: an RFC-3339 timestamp, or a duration like 5m meaning that
+ long ago. Defaults to 5m. Ignored when offset is set.
+
+ type: Restrict results to these event types, such as page_crashed or
+ captcha_challenge_result. Repeat the parameter for multiple values. Combines
+ with category: when both are set an event must match both.
+
+ until: End of the window (exclusive): an RFC-3339 timestamp, or a duration like 5m
+ meaning that long ago.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return self._get_api_list(
+ path_template("/browsers/{id_or_name}/telemetry/events", id_or_name=id_or_name),
+ page=AsyncOffsetPagination[TelemetryEventsResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "category": category,
+ "limit": limit,
+ "offset": offset,
+ "order": order,
+ "since": since,
+ "type": type,
+ "until": until,
+ },
+ telemetry_events_params.TelemetryEventsParams,
+ ),
+ ),
+ model=TelemetryEventsResponse,
+ )
+
+ async def stream(
+ self,
+ id_or_name: str,
+ *,
+ replay: str | Omit = omit,
+ type: SequenceNotStr[str] | Omit = omit,
+ last_event_id: str | 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,
+ ) -> AsyncStream[TelemetryStreamResponse]:
+ """Streams browser telemetry events as a server-sent events (SSE) stream.
+
+ The
+ stream closes when the browser session terminates. Each event frame includes an
+ id: field containing a monotonically increasing sequence number; pass it as
+ Last-Event-ID on reconnect to resume without gaps. The event: field is never
+ set; event frames carry JSON in the data: field. A frame with an id: field and
+ no data: field moves Last-Event-ID past events that were not delivered, because
+ a type filter excluded them or they could not be decoded or exceeded the size
+ limit; SSE clients dispatch no event for it. A keepalive comment frame is sent
+ every 15 seconds when no events arrive. Returns 404 if the browser session does
+ not exist. If telemetry was not enabled on the session, the stream opens but no
+ events are delivered. Fresh connections only see new events; pass replay=all to
+ start from the oldest retained event instead.
+
+ Args:
+ replay: Pass `all` to start from the oldest retained event instead of only new events;
+ any other value is treated as from-now. The buffer is bounded, so the first
+ event id may be greater than 1 if older events were evicted.
+
+ type: Deliver only these event types, such as captcha_solve_started or
+ captcha_challenge_result. Repeat the parameter or pass comma-separated values
+ for multiple types. Keepalive frames are always delivered. Filtered-out events
+ are not sent. An id-only frame carrying the latest skipped id is sent about
+ every 15 seconds while skipped events keep arriving, and otherwise with the next
+ keepalive or when the stream ends, so the connection stays active and
+ Last-Event-ID moves past them; a reconnect resumes after the skipped events.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})}
+ extra_headers = {**strip_not_given({"Last-Event-ID": last_event_id}), **(extra_headers or {})}
+ return await self._get(
+ path_template("/browsers/{id_or_name}/telemetry/stream", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "replay": replay,
+ "type": type,
+ },
+ telemetry_stream_params.TelemetryStreamParams,
+ ),
+ ),
+ cast_to=TelemetryStreamResponse,
+ stream=True,
+ stream_cls=AsyncStream[TelemetryStreamResponse],
+ )
+
+
+class TelemetryResourceWithRawResponse:
+ def __init__(self, telemetry: TelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ self.events = to_raw_response_wrapper(
+ telemetry.events,
+ )
+ self.stream = to_raw_response_wrapper(
+ telemetry.stream,
+ )
+
+
+class AsyncTelemetryResourceWithRawResponse:
+ def __init__(self, telemetry: AsyncTelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ self.events = async_to_raw_response_wrapper(
+ telemetry.events,
+ )
+ self.stream = async_to_raw_response_wrapper(
+ telemetry.stream,
+ )
+
+
+class TelemetryResourceWithStreamingResponse:
+ def __init__(self, telemetry: TelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ self.events = to_streamed_response_wrapper(
+ telemetry.events,
+ )
+ self.stream = to_streamed_response_wrapper(
+ telemetry.stream,
+ )
+
+
+class AsyncTelemetryResourceWithStreamingResponse:
+ def __init__(self, telemetry: AsyncTelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ self.events = async_to_streamed_response_wrapper(
+ telemetry.events,
+ )
+ self.stream = async_to_streamed_response_wrapper(
+ telemetry.stream,
+ )
diff --git a/src/kernel/resources/browsers/webmcp/__init__.py b/src/kernel/resources/browsers/webmcp/__init__.py
new file mode 100644
index 00000000..890b007a
--- /dev/null
+++ b/src/kernel/resources/browsers/webmcp/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .webmcp import (
+ WebmcpResource,
+ AsyncWebmcpResource,
+ WebmcpResourceWithRawResponse,
+ AsyncWebmcpResourceWithRawResponse,
+ WebmcpResourceWithStreamingResponse,
+ AsyncWebmcpResourceWithStreamingResponse,
+)
+from .custom_tools import (
+ CustomToolsResource,
+ AsyncCustomToolsResource,
+ CustomToolsResourceWithRawResponse,
+ AsyncCustomToolsResourceWithRawResponse,
+ CustomToolsResourceWithStreamingResponse,
+ AsyncCustomToolsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "CustomToolsResource",
+ "AsyncCustomToolsResource",
+ "CustomToolsResourceWithRawResponse",
+ "AsyncCustomToolsResourceWithRawResponse",
+ "CustomToolsResourceWithStreamingResponse",
+ "AsyncCustomToolsResourceWithStreamingResponse",
+ "WebmcpResource",
+ "AsyncWebmcpResource",
+ "WebmcpResourceWithRawResponse",
+ "AsyncWebmcpResourceWithRawResponse",
+ "WebmcpResourceWithStreamingResponse",
+ "AsyncWebmcpResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/browsers/webmcp/custom_tools.py b/src/kernel/resources/browsers/webmcp/custom_tools.py
new file mode 100644
index 00000000..e5126306
--- /dev/null
+++ b/src/kernel/resources/browsers/webmcp/custom_tools.py
@@ -0,0 +1,401 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+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 ...._base_client import make_request_options
+from ....types.browsers.webmcp import custom_tool_add_params
+from ....types.browsers.webmcp.custom_tools_response import CustomToolsResponse
+
+__all__ = ["CustomToolsResource", "AsyncCustomToolsResource"]
+
+
+class CustomToolsResource(SyncAPIResource):
+ """Discover and invoke native page tools across the browser instance."""
+
+ @cached_property
+ def with_raw_response(self) -> CustomToolsResourceWithRawResponse:
+ """
+ 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 CustomToolsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CustomToolsResourceWithStreamingResponse:
+ """
+ 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 CustomToolsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CustomToolsResponse:
+ """
+ Returns every registered custom tool with its generated ID, namespace, matcher,
+ and MCP tool metadata.
+
+ 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_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("/browsers/{id_or_name}/webmcp/custom-tools", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CustomToolsResponse,
+ )
+
+ def add(
+ self,
+ id_or_name: str,
+ *,
+ namespace: str,
+ source: str,
+ force_overwrite_namespace: bool | 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,
+ ) -> CustomToolsResponse:
+ """Add a namespaced batch of custom tools.
+
+ A custom tool can be page-backed or
+ CDP-backed. Page-backed tools execute in the page via JavaScript. CDP-backed
+ tools execute via CDP and can use all browser REPL tools (see `/repl`). The
+ source must evaluate to a non-empty array of definitions with URL matchers, tool
+ metadata (including an optional output schema), and execute functions. The batch
+ is added atomically. Matchers apply to top-level documents and nested frames,
+ including out-of-process iframes; each matching tool is exposed once on the
+ tab's top-level document and appears in the WebMCP tool snapshot.
+
+ To update one tool, list the tools, delete its ID, and add its replacement. Set
+ force_overwrite_namespace to replace every existing tool in the namespace
+ atomically; omitted or false adds tools without replacing existing ones.
+ Existing invocations continue.
+
+ Args:
+ source: JavaScript expression that evaluates to a non-empty array of custom tool
+ definitions. Limited to 8,000,000 bytes when UTF-8 encoded, so multi-byte
+ characters reduce the allowed character count.
+
+ force_overwrite_namespace: Atomically replace all existing tools in this namespace with this batch when
+ true.
+
+ 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_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("/browsers/{id_or_name}/webmcp/custom-tools", id_or_name=id_or_name),
+ body=maybe_transform(
+ {
+ "namespace": namespace,
+ "source": source,
+ "force_overwrite_namespace": force_overwrite_namespace,
+ },
+ custom_tool_add_params.CustomToolAddParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CustomToolsResponse,
+ )
+
+ def remove(
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Removes one custom tool by generated ID.
+
+ An invocation already in progress is
+ not canceled.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ 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("/browsers/{id_or_name}/webmcp/custom-tools/{id}", id_or_name=id_or_name, id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncCustomToolsResource(AsyncAPIResource):
+ """Discover and invoke native page tools across the browser instance."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncCustomToolsResourceWithRawResponse:
+ """
+ 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 AsyncCustomToolsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCustomToolsResourceWithStreamingResponse:
+ """
+ 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 AsyncCustomToolsResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CustomToolsResponse:
+ """
+ Returns every registered custom tool with its generated ID, namespace, matcher,
+ and MCP tool metadata.
+
+ 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_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("/browsers/{id_or_name}/webmcp/custom-tools", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CustomToolsResponse,
+ )
+
+ async def add(
+ self,
+ id_or_name: str,
+ *,
+ namespace: str,
+ source: str,
+ force_overwrite_namespace: bool | 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,
+ ) -> CustomToolsResponse:
+ """Add a namespaced batch of custom tools.
+
+ A custom tool can be page-backed or
+ CDP-backed. Page-backed tools execute in the page via JavaScript. CDP-backed
+ tools execute via CDP and can use all browser REPL tools (see `/repl`). The
+ source must evaluate to a non-empty array of definitions with URL matchers, tool
+ metadata (including an optional output schema), and execute functions. The batch
+ is added atomically. Matchers apply to top-level documents and nested frames,
+ including out-of-process iframes; each matching tool is exposed once on the
+ tab's top-level document and appears in the WebMCP tool snapshot.
+
+ To update one tool, list the tools, delete its ID, and add its replacement. Set
+ force_overwrite_namespace to replace every existing tool in the namespace
+ atomically; omitted or false adds tools without replacing existing ones.
+ Existing invocations continue.
+
+ Args:
+ source: JavaScript expression that evaluates to a non-empty array of custom tool
+ definitions. Limited to 8,000,000 bytes when UTF-8 encoded, so multi-byte
+ characters reduce the allowed character count.
+
+ force_overwrite_namespace: Atomically replace all existing tools in this namespace with this batch when
+ true.
+
+ 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_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("/browsers/{id_or_name}/webmcp/custom-tools", id_or_name=id_or_name),
+ body=await async_maybe_transform(
+ {
+ "namespace": namespace,
+ "source": source,
+ "force_overwrite_namespace": force_overwrite_namespace,
+ },
+ custom_tool_add_params.CustomToolAddParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CustomToolsResponse,
+ )
+
+ async def remove(
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Removes one custom tool by generated ID.
+
+ An invocation already in progress is
+ not canceled.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ 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("/browsers/{id_or_name}/webmcp/custom-tools/{id}", id_or_name=id_or_name, id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class CustomToolsResourceWithRawResponse:
+ def __init__(self, custom_tools: CustomToolsResource) -> None:
+ self._custom_tools = custom_tools
+
+ self.list = to_raw_response_wrapper(
+ custom_tools.list,
+ )
+ self.add = to_raw_response_wrapper(
+ custom_tools.add,
+ )
+ self.remove = to_raw_response_wrapper(
+ custom_tools.remove,
+ )
+
+
+class AsyncCustomToolsResourceWithRawResponse:
+ def __init__(self, custom_tools: AsyncCustomToolsResource) -> None:
+ self._custom_tools = custom_tools
+
+ self.list = async_to_raw_response_wrapper(
+ custom_tools.list,
+ )
+ self.add = async_to_raw_response_wrapper(
+ custom_tools.add,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ custom_tools.remove,
+ )
+
+
+class CustomToolsResourceWithStreamingResponse:
+ def __init__(self, custom_tools: CustomToolsResource) -> None:
+ self._custom_tools = custom_tools
+
+ self.list = to_streamed_response_wrapper(
+ custom_tools.list,
+ )
+ self.add = to_streamed_response_wrapper(
+ custom_tools.add,
+ )
+ self.remove = to_streamed_response_wrapper(
+ custom_tools.remove,
+ )
+
+
+class AsyncCustomToolsResourceWithStreamingResponse:
+ def __init__(self, custom_tools: AsyncCustomToolsResource) -> None:
+ self._custom_tools = custom_tools
+
+ self.list = async_to_streamed_response_wrapper(
+ custom_tools.list,
+ )
+ self.add = async_to_streamed_response_wrapper(
+ custom_tools.add,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ custom_tools.remove,
+ )
diff --git a/src/kernel/resources/browsers/webmcp/webmcp.py b/src/kernel/resources/browsers/webmcp/webmcp.py
new file mode 100644
index 00000000..dc030e34
--- /dev/null
+++ b/src/kernel/resources/browsers/webmcp/webmcp.py
@@ -0,0 +1,360 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+
+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 .custom_tools import (
+ CustomToolsResource,
+ AsyncCustomToolsResource,
+ CustomToolsResourceWithRawResponse,
+ AsyncCustomToolsResourceWithRawResponse,
+ CustomToolsResourceWithStreamingResponse,
+ AsyncCustomToolsResourceWithStreamingResponse,
+)
+from ...._base_client import make_request_options
+from ....types.browsers import webmcp_list_tools_params, webmcp_invoke_tool_params
+from ....types.browsers.tools_response import ToolsResponse
+from ....types.browsers.invocation_result import InvocationResult
+
+__all__ = ["WebmcpResource", "AsyncWebmcpResource"]
+
+
+class WebmcpResource(SyncAPIResource):
+ """Discover and invoke native page tools across the browser instance."""
+
+ @cached_property
+ def custom_tools(self) -> CustomToolsResource:
+ """Discover and invoke native page tools across the browser instance."""
+ return CustomToolsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> WebmcpResourceWithRawResponse:
+ """
+ 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 WebmcpResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> WebmcpResourceWithStreamingResponse:
+ """
+ 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 WebmcpResourceWithStreamingResponse(self)
+
+ def invoke_tool(
+ self,
+ id_or_name: str,
+ *,
+ input: Dict[str, object],
+ tool_ref: str,
+ timeout_sec: 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,
+ ) -> InvocationResult:
+ """Invokes the exact live registration identified by tool_ref.
+
+ Non-autosubmit
+ declarative form tools return after their fields are populated with an
+ awaiting_submission status. Other tools wait for a terminal result, including
+ across navigation. Inspect a populated form, obtain any required confirmation,
+ then submit through Playwright or computer interaction without invoking the tool
+ again. If the tab or embedded frame disappears, or the request times out after
+ invocation begins, the response reports outcome_unknown and the tool is not
+ retried. CDP-backed custom tool outputs above 240 KiB return an error rather
+ than a truncated result.
+
+ Args:
+ input: Tool input, limited to 1 MiB after JSON serialization.
+
+ 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_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("/browsers/{id_or_name}/webmcp/invoke", id_or_name=id_or_name),
+ body=maybe_transform(
+ {
+ "input": input,
+ "tool_ref": tool_ref,
+ "timeout_sec": timeout_sec,
+ },
+ webmcp_invoke_tool_params.WebmcpInvokeToolParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=InvocationResult,
+ )
+
+ def list_tools(
+ self,
+ id_or_name: str,
+ *,
+ exclude_custom: bool | 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,
+ ) -> ToolsResponse:
+ """
+ Returns a snapshot of native and custom WebMCP tools available across every open
+ tab and embedded frame in the browser. Each tool includes an opaque tool_ref for
+ invoking that exact live registration, nested tool metadata, and source
+ information. Custom tools include their generated ID, namespace, and CDP
+ target_id in source. Tools disappear when their document closes or navigates
+ away. Use exclude_custom to return only page-provided tools.
+
+ Args:
+ exclude_custom: Exclude custom tools when true.
+
+ 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_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("/browsers/{id_or_name}/webmcp/tools", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"exclude_custom": exclude_custom}, webmcp_list_tools_params.WebmcpListToolsParams
+ ),
+ ),
+ cast_to=ToolsResponse,
+ )
+
+
+class AsyncWebmcpResource(AsyncAPIResource):
+ """Discover and invoke native page tools across the browser instance."""
+
+ @cached_property
+ def custom_tools(self) -> AsyncCustomToolsResource:
+ """Discover and invoke native page tools across the browser instance."""
+ return AsyncCustomToolsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncWebmcpResourceWithRawResponse:
+ """
+ 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 AsyncWebmcpResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncWebmcpResourceWithStreamingResponse:
+ """
+ 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 AsyncWebmcpResourceWithStreamingResponse(self)
+
+ async def invoke_tool(
+ self,
+ id_or_name: str,
+ *,
+ input: Dict[str, object],
+ tool_ref: str,
+ timeout_sec: 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,
+ ) -> InvocationResult:
+ """Invokes the exact live registration identified by tool_ref.
+
+ Non-autosubmit
+ declarative form tools return after their fields are populated with an
+ awaiting_submission status. Other tools wait for a terminal result, including
+ across navigation. Inspect a populated form, obtain any required confirmation,
+ then submit through Playwright or computer interaction without invoking the tool
+ again. If the tab or embedded frame disappears, or the request times out after
+ invocation begins, the response reports outcome_unknown and the tool is not
+ retried. CDP-backed custom tool outputs above 240 KiB return an error rather
+ than a truncated result.
+
+ Args:
+ input: Tool input, limited to 1 MiB after JSON serialization.
+
+ 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_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("/browsers/{id_or_name}/webmcp/invoke", id_or_name=id_or_name),
+ body=await async_maybe_transform(
+ {
+ "input": input,
+ "tool_ref": tool_ref,
+ "timeout_sec": timeout_sec,
+ },
+ webmcp_invoke_tool_params.WebmcpInvokeToolParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=InvocationResult,
+ )
+
+ async def list_tools(
+ self,
+ id_or_name: str,
+ *,
+ exclude_custom: bool | 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,
+ ) -> ToolsResponse:
+ """
+ Returns a snapshot of native and custom WebMCP tools available across every open
+ tab and embedded frame in the browser. Each tool includes an opaque tool_ref for
+ invoking that exact live registration, nested tool metadata, and source
+ information. Custom tools include their generated ID, namespace, and CDP
+ target_id in source. Tools disappear when their document closes or navigates
+ away. Use exclude_custom to return only page-provided tools.
+
+ Args:
+ exclude_custom: Exclude custom tools when true.
+
+ 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_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("/browsers/{id_or_name}/webmcp/tools", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"exclude_custom": exclude_custom}, webmcp_list_tools_params.WebmcpListToolsParams
+ ),
+ ),
+ cast_to=ToolsResponse,
+ )
+
+
+class WebmcpResourceWithRawResponse:
+ def __init__(self, webmcp: WebmcpResource) -> None:
+ self._webmcp = webmcp
+
+ self.invoke_tool = to_raw_response_wrapper(
+ webmcp.invoke_tool,
+ )
+ self.list_tools = to_raw_response_wrapper(
+ webmcp.list_tools,
+ )
+
+ @cached_property
+ def custom_tools(self) -> CustomToolsResourceWithRawResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return CustomToolsResourceWithRawResponse(self._webmcp.custom_tools)
+
+
+class AsyncWebmcpResourceWithRawResponse:
+ def __init__(self, webmcp: AsyncWebmcpResource) -> None:
+ self._webmcp = webmcp
+
+ self.invoke_tool = async_to_raw_response_wrapper(
+ webmcp.invoke_tool,
+ )
+ self.list_tools = async_to_raw_response_wrapper(
+ webmcp.list_tools,
+ )
+
+ @cached_property
+ def custom_tools(self) -> AsyncCustomToolsResourceWithRawResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return AsyncCustomToolsResourceWithRawResponse(self._webmcp.custom_tools)
+
+
+class WebmcpResourceWithStreamingResponse:
+ def __init__(self, webmcp: WebmcpResource) -> None:
+ self._webmcp = webmcp
+
+ self.invoke_tool = to_streamed_response_wrapper(
+ webmcp.invoke_tool,
+ )
+ self.list_tools = to_streamed_response_wrapper(
+ webmcp.list_tools,
+ )
+
+ @cached_property
+ def custom_tools(self) -> CustomToolsResourceWithStreamingResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return CustomToolsResourceWithStreamingResponse(self._webmcp.custom_tools)
+
+
+class AsyncWebmcpResourceWithStreamingResponse:
+ def __init__(self, webmcp: AsyncWebmcpResource) -> None:
+ self._webmcp = webmcp
+
+ self.invoke_tool = async_to_streamed_response_wrapper(
+ webmcp.invoke_tool,
+ )
+ self.list_tools = async_to_streamed_response_wrapper(
+ webmcp.list_tools,
+ )
+
+ @cached_property
+ def custom_tools(self) -> AsyncCustomToolsResourceWithStreamingResponse:
+ """Discover and invoke native page tools across the browser instance."""
+ return AsyncCustomToolsResourceWithStreamingResponse(self._webmcp.custom_tools)
diff --git a/src/kernel/resources/config_registry/__init__.py b/src/kernel/resources/config_registry/__init__.py
new file mode 100644
index 00000000..9e904868
--- /dev/null
+++ b/src/kernel/resources/config_registry/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .analyses import (
+ AnalysesResource,
+ AsyncAnalysesResource,
+ AnalysesResourceWithRawResponse,
+ AsyncAnalysesResourceWithRawResponse,
+ AnalysesResourceWithStreamingResponse,
+ AsyncAnalysesResourceWithStreamingResponse,
+)
+from .config_registry import (
+ ConfigRegistryResource,
+ AsyncConfigRegistryResource,
+ ConfigRegistryResourceWithRawResponse,
+ AsyncConfigRegistryResourceWithRawResponse,
+ ConfigRegistryResourceWithStreamingResponse,
+ AsyncConfigRegistryResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "AnalysesResource",
+ "AsyncAnalysesResource",
+ "AnalysesResourceWithRawResponse",
+ "AsyncAnalysesResourceWithRawResponse",
+ "AnalysesResourceWithStreamingResponse",
+ "AsyncAnalysesResourceWithStreamingResponse",
+ "ConfigRegistryResource",
+ "AsyncConfigRegistryResource",
+ "ConfigRegistryResourceWithRawResponse",
+ "AsyncConfigRegistryResourceWithRawResponse",
+ "ConfigRegistryResourceWithStreamingResponse",
+ "AsyncConfigRegistryResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/config_registry/analyses.py b/src/kernel/resources/config_registry/analyses.py
new file mode 100644
index 00000000..bbaeff64
--- /dev/null
+++ b/src/kernel/resources/config_registry/analyses.py
@@ -0,0 +1,472 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import time
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import path_template, 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.config_registry import analysis_list_params
+from ...types.analysis_summary import AnalysisSummary
+from ...lib.config_registry_wait import (
+ DEFAULT_CONFIG_REGISTRY_POLL_INTERVAL,
+ poll_delay,
+ poll_headers,
+ analysis_finished,
+ wait_timeout_error,
+ validate_wait_options,
+)
+from ...types.config_registry_response import ConfigRegistryResponse
+
+__all__ = ["AnalysesResource", "AsyncAnalysesResource"]
+
+
+class AnalysesResource(SyncAPIResource):
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+
+ @cached_property
+ def with_raw_response(self) -> AnalysesResourceWithRawResponse:
+ """
+ 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 AnalysesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AnalysesResourceWithStreamingResponse:
+ """
+ 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 AnalysesResourceWithStreamingResponse(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,
+ ) -> ConfigRegistryResponse:
+ """
+ Returns a project-scoped historical analysis and the recommendation outcome
+ concluded by that run. Later knowledge does not change this response.
+
+ 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("/config-registry/analyses/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigRegistryResponse,
+ )
+
+ def wait_for_result(
+ self,
+ id: str,
+ *,
+ poll_interval: float = DEFAULT_CONFIG_REGISTRY_POLL_INTERVAL,
+ max_wait_seconds: float | None = None,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigRegistryResponse:
+ """Wait for an analysis to finish and return its complete result.
+
+ The first retrieval happens immediately. ``max_wait_seconds`` is a soft
+ polling deadline: an in-flight request and its normal retries may finish
+ after it. Timing out does not cancel the remote analysis.
+ """
+ validate_wait_options(poll_interval, max_wait_seconds)
+ started_at = time.monotonic()
+ deadline = started_at + max_wait_seconds if max_wait_seconds is not None else None
+ headers = poll_headers(extra_headers)
+ polls = 0
+ last_status: str | None = None
+
+ while True:
+ if polls > 0 and deadline is not None and time.monotonic() >= deadline:
+ raise wait_timeout_error(id, polls, last_status, started_at)
+
+ response = self.retrieve(
+ id,
+ extra_headers=headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ )
+ polls += 1
+ finished, last_status = analysis_finished(response, id)
+ if finished:
+ return response
+
+ delay = poll_delay(poll_interval)
+ if deadline is not None:
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ raise wait_timeout_error(id, polls, last_status, started_at)
+ delay = min(delay, remaining)
+ self._sleep(delay)
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ search: str | 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[AnalysisSummary]:
+ """
+ Lists analyses for the selected project, newest first.
+
+ Args:
+ search: Case-insensitive substring search over requested URLs.
+
+ 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(
+ "/config-registry/analyses",
+ page=SyncOffsetPagination[AnalysisSummary],
+ 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,
+ "search": search,
+ },
+ analysis_list_params.AnalysisListParams,
+ ),
+ ),
+ model=AnalysisSummary,
+ )
+
+ def cancel(
+ 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,
+ ) -> ConfigRegistryResponse:
+ """Requests cancellation of a running project-scoped analysis.
+
+ Cancellation is
+ asynchronous; poll the analysis until its status becomes canceled. Repeating the
+ request after the analysis reaches a terminal state returns the existing
+ outcome.
+
+ 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._post(
+ path_template("/config-registry/analyses/{id}/cancel", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigRegistryResponse,
+ )
+
+
+class AsyncAnalysesResource(AsyncAPIResource):
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncAnalysesResourceWithRawResponse:
+ """
+ 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 AsyncAnalysesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAnalysesResourceWithStreamingResponse:
+ """
+ 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 AsyncAnalysesResourceWithStreamingResponse(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,
+ ) -> ConfigRegistryResponse:
+ """
+ Returns a project-scoped historical analysis and the recommendation outcome
+ concluded by that run. Later knowledge does not change this response.
+
+ 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("/config-registry/analyses/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigRegistryResponse,
+ )
+
+ async def wait_for_result(
+ self,
+ id: str,
+ *,
+ poll_interval: float = DEFAULT_CONFIG_REGISTRY_POLL_INTERVAL,
+ max_wait_seconds: float | None = None,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigRegistryResponse:
+ """Wait for an analysis to finish and return its complete result.
+
+ The first retrieval happens immediately. ``max_wait_seconds`` is a soft
+ polling deadline: an in-flight request and its normal retries may finish
+ after it. Timing out does not cancel the remote analysis. Cancelling the
+ calling task stops the wait without cancelling the remote analysis.
+ """
+ validate_wait_options(poll_interval, max_wait_seconds)
+ started_at = time.monotonic()
+ deadline = started_at + max_wait_seconds if max_wait_seconds is not None else None
+ headers = poll_headers(extra_headers)
+ polls = 0
+ last_status: str | None = None
+
+ while True:
+ if polls > 0 and deadline is not None and time.monotonic() >= deadline:
+ raise wait_timeout_error(id, polls, last_status, started_at)
+
+ response = await self.retrieve(
+ id,
+ extra_headers=headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ )
+ polls += 1
+ finished, last_status = analysis_finished(response, id)
+ if finished:
+ return response
+
+ delay = poll_delay(poll_interval)
+ if deadline is not None:
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ raise wait_timeout_error(id, polls, last_status, started_at)
+ delay = min(delay, remaining)
+ await self._sleep(delay)
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ search: str | 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[AnalysisSummary, AsyncOffsetPagination[AnalysisSummary]]:
+ """
+ Lists analyses for the selected project, newest first.
+
+ Args:
+ search: Case-insensitive substring search over requested URLs.
+
+ 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(
+ "/config-registry/analyses",
+ page=AsyncOffsetPagination[AnalysisSummary],
+ 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,
+ "search": search,
+ },
+ analysis_list_params.AnalysisListParams,
+ ),
+ ),
+ model=AnalysisSummary,
+ )
+
+ async def cancel(
+ 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,
+ ) -> ConfigRegistryResponse:
+ """Requests cancellation of a running project-scoped analysis.
+
+ Cancellation is
+ asynchronous; poll the analysis until its status becomes canceled. Repeating the
+ request after the analysis reaches a terminal state returns the existing
+ outcome.
+
+ 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._post(
+ path_template("/config-registry/analyses/{id}/cancel", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigRegistryResponse,
+ )
+
+
+class AnalysesResourceWithRawResponse:
+ def __init__(self, analyses: AnalysesResource) -> None:
+ self._analyses = analyses
+
+ self.retrieve = to_raw_response_wrapper(
+ analyses.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ analyses.list,
+ )
+ self.cancel = to_raw_response_wrapper(
+ analyses.cancel,
+ )
+
+
+class AsyncAnalysesResourceWithRawResponse:
+ def __init__(self, analyses: AsyncAnalysesResource) -> None:
+ self._analyses = analyses
+
+ self.retrieve = async_to_raw_response_wrapper(
+ analyses.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ analyses.list,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ analyses.cancel,
+ )
+
+
+class AnalysesResourceWithStreamingResponse:
+ def __init__(self, analyses: AnalysesResource) -> None:
+ self._analyses = analyses
+
+ self.retrieve = to_streamed_response_wrapper(
+ analyses.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ analyses.list,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ analyses.cancel,
+ )
+
+
+class AsyncAnalysesResourceWithStreamingResponse:
+ def __init__(self, analyses: AsyncAnalysesResource) -> None:
+ self._analyses = analyses
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ analyses.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ analyses.list,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ analyses.cancel,
+ )
diff --git a/src/kernel/resources/config_registry/config_registry.py b/src/kernel/resources/config_registry/config_registry.py
new file mode 100644
index 00000000..39faa9f5
--- /dev/null
+++ b/src/kernel/resources/config_registry/config_registry.py
@@ -0,0 +1,484 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ...types import config_registry_list_params, config_registry_lookup_params, config_registry_resolve_params
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from .analyses import (
+ AnalysesResource,
+ AsyncAnalysesResource,
+ AnalysesResourceWithRawResponse,
+ AsyncAnalysesResourceWithRawResponse,
+ AnalysesResourceWithStreamingResponse,
+ AsyncAnalysesResourceWithStreamingResponse,
+)
+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.lookup_response import LookupResponse
+from ...types.recommendation_summary import RecommendationSummary
+from ...types.config_registry_response import ConfigRegistryResponse
+
+__all__ = ["ConfigRegistryResource", "AsyncConfigRegistryResource"]
+
+
+class ConfigRegistryResource(SyncAPIResource):
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+
+ @cached_property
+ def analyses(self) -> AnalysesResource:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ return AnalysesResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> ConfigRegistryResourceWithRawResponse:
+ """
+ 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 ConfigRegistryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ConfigRegistryResourceWithStreamingResponse:
+ """
+ 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 ConfigRegistryResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ search: str | Omit = omit,
+ sort_by: Literal["target", "analysis_status", "recommended_config", "last_requested_at", "success_rate"]
+ | Omit = omit,
+ sort_order: Literal["asc", "desc"] | 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[RecommendationSummary]:
+ """
+ Lists unique exact targets previously analyzed by the selected project with the
+ recommendation produced by each target's latest analysis.
+
+ Args:
+ search: Case-insensitive substring search over normalized targets, including domain,
+ subdomain, and path.
+
+ 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(
+ "/config-registry",
+ page=SyncOffsetPagination[RecommendationSummary],
+ 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,
+ "search": search,
+ "sort_by": sort_by,
+ "sort_order": sort_order,
+ },
+ config_registry_list_params.ConfigRegistryListParams,
+ ),
+ ),
+ model=RecommendationSummary,
+ )
+
+ def lookup(
+ self,
+ *,
+ url: str,
+ allowed_proxy_countries: SequenceNotStr[str] | 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,
+ ) -> LookupResponse:
+ """
+ Returns current global knowledge without resolving DNS, creating an analysis, or
+ updating config registry data.
+
+ Args:
+ url: Public HTTP(S) URL to look up.
+
+ allowed_proxy_countries: ISO 3166 country codes Kernel may use when returning a proxy configuration. When
+ omitted, Kernel uses its default country selection.
+
+ 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(
+ "/config-registry/lookup",
+ body=maybe_transform(
+ {
+ "url": url,
+ "allowed_proxy_countries": allowed_proxy_countries,
+ },
+ config_registry_lookup_params.ConfigRegistryLookupParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LookupResponse,
+ )
+
+ def resolve(
+ self,
+ *,
+ url: str,
+ allowed_proxy_countries: SequenceNotStr[str] | Omit = omit,
+ intent: str | 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,
+ ) -> ConfigRegistryResponse:
+ """
+ Explicitly starts or retries a project-scoped background analysis while
+ preserving current global knowledge when available. Use
+ `/config-registry/lookup` for side-effect-free reads.
+
+ Args:
+ url: Public HTTP(S) URL to refresh.
+
+ allowed_proxy_countries: ISO 3166 country codes Kernel may use when searching for or returning a proxy
+ configuration. Kernel may test a subset of allowed countries. When omitted,
+ Kernel uses its default country selection.
+
+ intent: Plain-language description of the workload you intend to run against this
+ target, in a sentence or two. Requires an https target, because the pass treats
+ any non-HTTPS destination as off-site and will not drive an http one. Kernel
+ uses it to drive the browser further into the site, where it can observe
+ protections that only appear once a session interacts. When this target already
+ has a recommended configuration, the run confirms that one instead of
+ re-deriving the whole matrix, so supplying an intent narrows what can be
+ recommended.
+
+ 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(
+ "/config-registry/resolve",
+ body=maybe_transform(
+ {
+ "url": url,
+ "allowed_proxy_countries": allowed_proxy_countries,
+ "intent": intent,
+ },
+ config_registry_resolve_params.ConfigRegistryResolveParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigRegistryResponse,
+ )
+
+
+class AsyncConfigRegistryResource(AsyncAPIResource):
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+
+ @cached_property
+ def analyses(self) -> AsyncAnalysesResource:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ return AsyncAnalysesResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncConfigRegistryResourceWithRawResponse:
+ """
+ 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 AsyncConfigRegistryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncConfigRegistryResourceWithStreamingResponse:
+ """
+ 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 AsyncConfigRegistryResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ search: str | Omit = omit,
+ sort_by: Literal["target", "analysis_status", "recommended_config", "last_requested_at", "success_rate"]
+ | Omit = omit,
+ sort_order: Literal["asc", "desc"] | 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[RecommendationSummary, AsyncOffsetPagination[RecommendationSummary]]:
+ """
+ Lists unique exact targets previously analyzed by the selected project with the
+ recommendation produced by each target's latest analysis.
+
+ Args:
+ search: Case-insensitive substring search over normalized targets, including domain,
+ subdomain, and path.
+
+ 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(
+ "/config-registry",
+ page=AsyncOffsetPagination[RecommendationSummary],
+ 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,
+ "search": search,
+ "sort_by": sort_by,
+ "sort_order": sort_order,
+ },
+ config_registry_list_params.ConfigRegistryListParams,
+ ),
+ ),
+ model=RecommendationSummary,
+ )
+
+ async def lookup(
+ self,
+ *,
+ url: str,
+ allowed_proxy_countries: SequenceNotStr[str] | 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,
+ ) -> LookupResponse:
+ """
+ Returns current global knowledge without resolving DNS, creating an analysis, or
+ updating config registry data.
+
+ Args:
+ url: Public HTTP(S) URL to look up.
+
+ allowed_proxy_countries: ISO 3166 country codes Kernel may use when returning a proxy configuration. When
+ omitted, Kernel uses its default country selection.
+
+ 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(
+ "/config-registry/lookup",
+ body=await async_maybe_transform(
+ {
+ "url": url,
+ "allowed_proxy_countries": allowed_proxy_countries,
+ },
+ config_registry_lookup_params.ConfigRegistryLookupParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LookupResponse,
+ )
+
+ async def resolve(
+ self,
+ *,
+ url: str,
+ allowed_proxy_countries: SequenceNotStr[str] | Omit = omit,
+ intent: str | 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,
+ ) -> ConfigRegistryResponse:
+ """
+ Explicitly starts or retries a project-scoped background analysis while
+ preserving current global knowledge when available. Use
+ `/config-registry/lookup` for side-effect-free reads.
+
+ Args:
+ url: Public HTTP(S) URL to refresh.
+
+ allowed_proxy_countries: ISO 3166 country codes Kernel may use when searching for or returning a proxy
+ configuration. Kernel may test a subset of allowed countries. When omitted,
+ Kernel uses its default country selection.
+
+ intent: Plain-language description of the workload you intend to run against this
+ target, in a sentence or two. Requires an https target, because the pass treats
+ any non-HTTPS destination as off-site and will not drive an http one. Kernel
+ uses it to drive the browser further into the site, where it can observe
+ protections that only appear once a session interacts. When this target already
+ has a recommended configuration, the run confirms that one instead of
+ re-deriving the whole matrix, so supplying an intent narrows what can be
+ recommended.
+
+ 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(
+ "/config-registry/resolve",
+ body=await async_maybe_transform(
+ {
+ "url": url,
+ "allowed_proxy_countries": allowed_proxy_countries,
+ "intent": intent,
+ },
+ config_registry_resolve_params.ConfigRegistryResolveParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigRegistryResponse,
+ )
+
+
+class ConfigRegistryResourceWithRawResponse:
+ def __init__(self, config_registry: ConfigRegistryResource) -> None:
+ self._config_registry = config_registry
+
+ self.list = to_raw_response_wrapper(
+ config_registry.list,
+ )
+ self.lookup = to_raw_response_wrapper(
+ config_registry.lookup,
+ )
+ self.resolve = to_raw_response_wrapper(
+ config_registry.resolve,
+ )
+
+ @cached_property
+ def analyses(self) -> AnalysesResourceWithRawResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ return AnalysesResourceWithRawResponse(self._config_registry.analyses)
+
+
+class AsyncConfigRegistryResourceWithRawResponse:
+ def __init__(self, config_registry: AsyncConfigRegistryResource) -> None:
+ self._config_registry = config_registry
+
+ self.list = async_to_raw_response_wrapper(
+ config_registry.list,
+ )
+ self.lookup = async_to_raw_response_wrapper(
+ config_registry.lookup,
+ )
+ self.resolve = async_to_raw_response_wrapper(
+ config_registry.resolve,
+ )
+
+ @cached_property
+ def analyses(self) -> AsyncAnalysesResourceWithRawResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ return AsyncAnalysesResourceWithRawResponse(self._config_registry.analyses)
+
+
+class ConfigRegistryResourceWithStreamingResponse:
+ def __init__(self, config_registry: ConfigRegistryResource) -> None:
+ self._config_registry = config_registry
+
+ self.list = to_streamed_response_wrapper(
+ config_registry.list,
+ )
+ self.lookup = to_streamed_response_wrapper(
+ config_registry.lookup,
+ )
+ self.resolve = to_streamed_response_wrapper(
+ config_registry.resolve,
+ )
+
+ @cached_property
+ def analyses(self) -> AnalysesResourceWithStreamingResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ return AnalysesResourceWithStreamingResponse(self._config_registry.analyses)
+
+
+class AsyncConfigRegistryResourceWithStreamingResponse:
+ def __init__(self, config_registry: AsyncConfigRegistryResource) -> None:
+ self._config_registry = config_registry
+
+ self.list = async_to_streamed_response_wrapper(
+ config_registry.list,
+ )
+ self.lookup = async_to_streamed_response_wrapper(
+ config_registry.lookup,
+ )
+ self.resolve = async_to_streamed_response_wrapper(
+ config_registry.resolve,
+ )
+
+ @cached_property
+ def analyses(self) -> AsyncAnalysesResourceWithStreamingResponse:
+ """Resolve browser and proxy recommendations for bot-protected sites."""
+ return AsyncAnalysesResourceWithStreamingResponse(self._config_registry.analyses)
diff --git a/src/kernel/resources/credential_providers.py b/src/kernel/resources/credential_providers.py
index 2dede2c4..6a003f99 100644
--- a/src/kernel/resources/credential_providers.py
+++ b/src/kernel/resources/credential_providers.py
@@ -6,7 +6,11 @@
import httpx
-from ..types import credential_provider_create_params, credential_provider_update_params
+from ..types import (
+ credential_provider_list_params,
+ credential_provider_create_params,
+ credential_provider_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
@@ -17,10 +21,10 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from .._base_client import make_request_options
+from ..pagination import SyncOffsetPagination, AsyncOffsetPagination
+from .._base_client import AsyncPaginator, make_request_options
from ..types.credential_provider import CredentialProvider
from ..types.credential_provider_test_result import CredentialProviderTestResult
-from ..types.credential_provider_list_response import CredentialProviderListResponse
from ..types.credential_provider_list_items_response import CredentialProviderListItemsResponse
__all__ = ["CredentialProvidersResource", "AsyncCredentialProvidersResource"]
@@ -69,7 +73,8 @@ def create(
Args:
token: Service account token for the provider (e.g., 1Password service account token)
- name: Human-readable name for this provider instance (unique per org)
+ name: Human-readable name for this provider instance (unique per org). Surrounding
+ whitespace is trimmed and the trimmed value must be non-empty.
provider_type: Type of credential provider
@@ -159,7 +164,8 @@ def update(
enabled: Whether the provider is enabled for credential lookups
- name: Human-readable name for this provider instance
+ name: Human-readable name for this provider instance. Surrounding whitespace is
+ trimmed and the trimmed value must be non-empty.
priority: Priority order for credential lookups (lower numbers are checked first)
@@ -194,20 +200,53 @@ def update(
def list(
self,
*,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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,
- ) -> CredentialProviderListResponse:
- """List external credential providers configured for the organization."""
- return self._get(
+ ) -> SyncOffsetPagination[CredentialProvider]:
+ """
+ List external credential providers configured for the organization.
+
+ Args:
+ limit: Limit the number of credential providers to return.
+
+ offset: Offset the number of credential providers to return.
+
+ query: Case-insensitive substring match against credential provider name. IDs match by
+ exact value.
+
+ 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(
"/org/credential_providers",
+ page=SyncOffsetPagination[CredentialProvider],
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ "query": query,
+ },
+ credential_provider_list_params.CredentialProviderListParams,
+ ),
),
- cast_to=CredentialProviderListResponse,
+ model=CredentialProvider,
)
def delete(
@@ -355,7 +394,8 @@ async def create(
Args:
token: Service account token for the provider (e.g., 1Password service account token)
- name: Human-readable name for this provider instance (unique per org)
+ name: Human-readable name for this provider instance (unique per org). Surrounding
+ whitespace is trimmed and the trimmed value must be non-empty.
provider_type: Type of credential provider
@@ -445,7 +485,8 @@ async def update(
enabled: Whether the provider is enabled for credential lookups
- name: Human-readable name for this provider instance
+ name: Human-readable name for this provider instance. Surrounding whitespace is
+ trimmed and the trimmed value must be non-empty.
priority: Priority order for credential lookups (lower numbers are checked first)
@@ -477,23 +518,56 @@ async def update(
cast_to=CredentialProvider,
)
- async def list(
+ def list(
self,
*,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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,
- ) -> CredentialProviderListResponse:
- """List external credential providers configured for the organization."""
- return await self._get(
+ ) -> AsyncPaginator[CredentialProvider, AsyncOffsetPagination[CredentialProvider]]:
+ """
+ List external credential providers configured for the organization.
+
+ Args:
+ limit: Limit the number of credential providers to return.
+
+ offset: Offset the number of credential providers to return.
+
+ query: Case-insensitive substring match against credential provider name. IDs match by
+ exact value.
+
+ 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(
"/org/credential_providers",
+ page=AsyncOffsetPagination[CredentialProvider],
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ "query": query,
+ },
+ credential_provider_list_params.CredentialProviderListParams,
+ ),
),
- cast_to=CredentialProviderListResponse,
+ model=CredentialProvider,
)
async def delete(
diff --git a/src/kernel/resources/credentials.py b/src/kernel/resources/credentials.py
index 7d00faab..47eb0aad 100644
--- a/src/kernel/resources/credentials.py
+++ b/src/kernel/resources/credentials.py
@@ -3,11 +3,12 @@
from __future__ import annotations
from typing import Dict, Optional
+from typing_extensions import Literal
import httpx
from ..types import credential_list_params, credential_create_params, credential_update_params
-from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
@@ -54,6 +55,9 @@ def create(
name: str,
values: Dict[str, str],
sso_provider: str | Omit = omit,
+ totp_algorithm: Literal["SHA1", "SHA256", "SHA512"] | Omit = omit,
+ totp_digits: int | Omit = omit,
+ totp_period: int | Omit = omit,
totp_secret: str | 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.
@@ -77,8 +81,20 @@ def create(
button, it will be clicked first before filling credential values on the
identity provider's login page.
- totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Used for automatic
- 2FA during login.
+ totp_algorithm: HMAC algorithm used to generate TOTP codes. Defaults to SHA1 and is ignored when
+ an `otpauth://` URI supplies the algorithm.
+
+ totp_digits: Number of digits in generated TOTP codes. Defaults to 6 and is ignored when an
+ `otpauth://` URI supplies the digit count.
+
+ totp_period: TOTP rotation period in seconds. Defaults to 30 and is ignored when an
+ `otpauth://` URI supplies the period.
+
+ totp_secret: Accepts a 16-128 character base32-encoded TOTP secret or an `otpauth://totp/...`
+ URI. The range accepts existing shorter seeds and longer seeds regardless of
+ HMAC algorithm; RFC 6238 recommends unpadded base32 lengths of 32/52/103 for
+ SHA1/SHA256/SHA512. Only URI parameters present override the corresponding
+ explicit TOTP fields. Used for automatic 2FA during login.
extra_headers: Send extra headers
@@ -96,6 +112,9 @@ def create(
"name": name,
"values": values,
"sso_provider": sso_provider,
+ "totp_algorithm": totp_algorithm,
+ "totp_digits": totp_digits,
+ "totp_period": totp_period,
"totp_secret": totp_secret,
},
credential_create_params.CredentialCreateParams,
@@ -145,7 +164,11 @@ def update(
id_or_name: str,
*,
name: str | Omit = omit,
+ remove_value_keys: SequenceNotStr[str] | Omit = omit,
sso_provider: Optional[str] | Omit = omit,
+ totp_algorithm: Literal["SHA1", "SHA256", "SHA512"] | Omit = omit,
+ totp_digits: int | Omit = omit,
+ totp_period: int | Omit = omit,
totp_secret: str | Omit = omit,
values: Dict[str, str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -163,11 +186,26 @@ def update(
Args:
name: New name for the credential
+ remove_value_keys: Field names to remove from the credential's stored values. Removals are applied
+ before `values` are merged, so a key present in both is kept with its new value.
+
sso_provider: If set, indicates this credential should be used with the specified SSO
provider. Set to empty string or null to remove.
- totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Spaces and
- formatting are automatically normalized. Set to empty string to remove.
+ totp_algorithm: HMAC algorithm used to generate TOTP codes. Requires totp_secret and is ignored
+ when an `otpauth://` URI supplies the algorithm.
+
+ totp_digits: Number of digits in generated TOTP codes. Requires totp_secret and is ignored
+ when an `otpauth://` URI supplies the digit count.
+
+ totp_period: TOTP rotation period in seconds. Requires totp_secret and is ignored when an
+ `otpauth://` URI supplies the period.
+
+ totp_secret: Accepts a 16-128 character base32-encoded TOTP secret or an `otpauth://totp/...`
+ URI. Only URI parameters present override the corresponding explicit TOTP
+ fields. When rotating a raw secret, omitted fields preserve their existing
+ values; a new URI defaults unspecified fields to SHA1/6/30. Set to empty string
+ to remove the secret and its metadata.
values: Field name to value mapping. Values are merged with existing values (new keys
added, existing keys overwritten).
@@ -187,7 +225,11 @@ def update(
body=maybe_transform(
{
"name": name,
+ "remove_value_keys": remove_value_keys,
"sso_provider": sso_provider,
+ "totp_algorithm": totp_algorithm,
+ "totp_digits": totp_digits,
+ "totp_period": totp_period,
"totp_secret": totp_secret,
"values": values,
},
@@ -205,6 +247,7 @@ def list(
domain: str | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
+ query: str | 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,
@@ -212,10 +255,9 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SyncOffsetPagination[Credential]:
- """List credentials owned by the caller's organization.
+ """List credentials in the resolved project.
- Credential values are not
- returned.
+ Credential values are not returned.
Args:
domain: Filter by domain
@@ -224,6 +266,9 @@ def list(
offset: Number of results to skip
+ query: Case-insensitive substring match against credential name or domain. IDs match by
+ exact value.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -245,6 +290,7 @@ def list(
"domain": domain,
"limit": limit,
"offset": offset,
+ "query": query,
},
credential_list_params.CredentialListParams,
),
@@ -351,6 +397,9 @@ async def create(
name: str,
values: Dict[str, str],
sso_provider: str | Omit = omit,
+ totp_algorithm: Literal["SHA1", "SHA256", "SHA512"] | Omit = omit,
+ totp_digits: int | Omit = omit,
+ totp_period: int | Omit = omit,
totp_secret: str | 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.
@@ -374,8 +423,20 @@ async def create(
button, it will be clicked first before filling credential values on the
identity provider's login page.
- totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Used for automatic
- 2FA during login.
+ totp_algorithm: HMAC algorithm used to generate TOTP codes. Defaults to SHA1 and is ignored when
+ an `otpauth://` URI supplies the algorithm.
+
+ totp_digits: Number of digits in generated TOTP codes. Defaults to 6 and is ignored when an
+ `otpauth://` URI supplies the digit count.
+
+ totp_period: TOTP rotation period in seconds. Defaults to 30 and is ignored when an
+ `otpauth://` URI supplies the period.
+
+ totp_secret: Accepts a 16-128 character base32-encoded TOTP secret or an `otpauth://totp/...`
+ URI. The range accepts existing shorter seeds and longer seeds regardless of
+ HMAC algorithm; RFC 6238 recommends unpadded base32 lengths of 32/52/103 for
+ SHA1/SHA256/SHA512. Only URI parameters present override the corresponding
+ explicit TOTP fields. Used for automatic 2FA during login.
extra_headers: Send extra headers
@@ -393,6 +454,9 @@ async def create(
"name": name,
"values": values,
"sso_provider": sso_provider,
+ "totp_algorithm": totp_algorithm,
+ "totp_digits": totp_digits,
+ "totp_period": totp_period,
"totp_secret": totp_secret,
},
credential_create_params.CredentialCreateParams,
@@ -442,7 +506,11 @@ async def update(
id_or_name: str,
*,
name: str | Omit = omit,
+ remove_value_keys: SequenceNotStr[str] | Omit = omit,
sso_provider: Optional[str] | Omit = omit,
+ totp_algorithm: Literal["SHA1", "SHA256", "SHA512"] | Omit = omit,
+ totp_digits: int | Omit = omit,
+ totp_period: int | Omit = omit,
totp_secret: str | Omit = omit,
values: Dict[str, str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -460,11 +528,26 @@ async def update(
Args:
name: New name for the credential
+ remove_value_keys: Field names to remove from the credential's stored values. Removals are applied
+ before `values` are merged, so a key present in both is kept with its new value.
+
sso_provider: If set, indicates this credential should be used with the specified SSO
provider. Set to empty string or null to remove.
- totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Spaces and
- formatting are automatically normalized. Set to empty string to remove.
+ totp_algorithm: HMAC algorithm used to generate TOTP codes. Requires totp_secret and is ignored
+ when an `otpauth://` URI supplies the algorithm.
+
+ totp_digits: Number of digits in generated TOTP codes. Requires totp_secret and is ignored
+ when an `otpauth://` URI supplies the digit count.
+
+ totp_period: TOTP rotation period in seconds. Requires totp_secret and is ignored when an
+ `otpauth://` URI supplies the period.
+
+ totp_secret: Accepts a 16-128 character base32-encoded TOTP secret or an `otpauth://totp/...`
+ URI. Only URI parameters present override the corresponding explicit TOTP
+ fields. When rotating a raw secret, omitted fields preserve their existing
+ values; a new URI defaults unspecified fields to SHA1/6/30. Set to empty string
+ to remove the secret and its metadata.
values: Field name to value mapping. Values are merged with existing values (new keys
added, existing keys overwritten).
@@ -484,7 +567,11 @@ async def update(
body=await async_maybe_transform(
{
"name": name,
+ "remove_value_keys": remove_value_keys,
"sso_provider": sso_provider,
+ "totp_algorithm": totp_algorithm,
+ "totp_digits": totp_digits,
+ "totp_period": totp_period,
"totp_secret": totp_secret,
"values": values,
},
@@ -502,6 +589,7 @@ def list(
domain: str | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
+ query: str | 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,
@@ -509,10 +597,9 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AsyncPaginator[Credential, AsyncOffsetPagination[Credential]]:
- """List credentials owned by the caller's organization.
+ """List credentials in the resolved project.
- Credential values are not
- returned.
+ Credential values are not returned.
Args:
domain: Filter by domain
@@ -521,6 +608,9 @@ def list(
offset: Number of results to skip
+ query: Case-insensitive substring match against credential name or domain. IDs match by
+ exact value.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -542,6 +632,7 @@ def list(
"domain": domain,
"limit": limit,
"offset": offset,
+ "query": query,
},
credential_list_params.CredentialListParams,
),
diff --git a/src/kernel/resources/deployments.py b/src/kernel/resources/deployments.py
index 9b079eb9..c6de9212 100644
--- a/src/kernel/resources/deployments.py
+++ b/src/kernel/resources/deployments.py
@@ -163,6 +163,7 @@ def list(
app_version: str | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
+ query: str | 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,
@@ -183,6 +184,8 @@ def list(
offset: Offset the number of deployments to return.
+ query: Search deployments by ID or app name.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -205,6 +208,7 @@ def list(
"app_version": app_version,
"limit": limit,
"offset": offset,
+ "query": query,
},
deployment_list_params.DeploymentListParams,
),
@@ -429,6 +433,7 @@ def list(
app_version: str | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
+ query: str | 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,
@@ -449,6 +454,8 @@ def list(
offset: Offset the number of deployments to return.
+ query: Search deployments by ID or app name.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -471,6 +478,7 @@ def list(
"app_version": app_version,
"limit": limit,
"offset": offset,
+ "query": query,
},
deployment_list_params.DeploymentListParams,
),
diff --git a/src/kernel/resources/extensions.py b/src/kernel/resources/extensions.py
index d5cf0eb7..81a1ff9a 100644
--- a/src/kernel/resources/extensions.py
+++ b/src/kernel/resources/extensions.py
@@ -7,7 +7,7 @@
import httpx
-from ..types import extension_upload_params, extension_download_from_chrome_store_params
+from ..types import extension_list_params, extension_upload_params, extension_download_from_chrome_store_params
from .._files import deepcopy_with_paths
from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, FileTypes, omit, not_given
from .._utils import extract_files, path_template, maybe_transform, async_maybe_transform
@@ -27,7 +27,9 @@
async_to_custom_raw_response_wrapper,
async_to_custom_streamed_response_wrapper,
)
-from .._base_client import make_request_options
+from ..pagination import SyncOffsetPagination, AsyncOffsetPagination
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.extension_get_response import ExtensionGetResponse
from ..types.extension_list_response import ExtensionListResponse
from ..types.extension_upload_response import ExtensionUploadResponse
@@ -59,20 +61,60 @@ def with_streaming_response(self) -> ExtensionsResourceWithStreamingResponse:
def list(
self,
*,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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,
- ) -> ExtensionListResponse:
- """List extensions owned by the caller's organization."""
- return self._get(
+ ) -> SyncOffsetPagination[ExtensionListResponse]:
+ """
+ List extensions in the resolved project.
+
+ Args:
+ limit: Limit the number of extensions to return.
+
+ name: Exact-match filter on extension name using the database collation. In
+ production, matching is case- and accent-insensitive. During the default-project
+ migration, unscoped requests prefer a concrete default-project extension over a
+ legacy unscoped extension with the same name.
+
+ offset: Offset the number of extensions to return.
+
+ query: Case-insensitive substring match against extension name. IDs match by exact
+ value.
+
+ 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(
"/extensions",
+ page=SyncOffsetPagination[ExtensionListResponse],
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ extension_list_params.ExtensionListParams,
+ ),
),
- cast_to=ExtensionListResponse,
+ model=ExtensionListResponse,
)
def delete(
@@ -191,6 +233,40 @@ def download_from_chrome_store(
cast_to=BinaryAPIResponse,
)
+ def get(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ExtensionGetResponse:
+ """
+ Get an extension's metadata (name, size, timestamps) by ID or name, without
+ downloading the archive.
+
+ 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_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("/extensions/{id_or_name}/metadata", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ExtensionGetResponse,
+ )
+
def upload(
self,
*,
@@ -266,23 +342,63 @@ def with_streaming_response(self) -> AsyncExtensionsResourceWithStreamingRespons
"""
return AsyncExtensionsResourceWithStreamingResponse(self)
- async def list(
+ def list(
self,
*,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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,
- ) -> ExtensionListResponse:
- """List extensions owned by the caller's organization."""
- return await self._get(
+ ) -> AsyncPaginator[ExtensionListResponse, AsyncOffsetPagination[ExtensionListResponse]]:
+ """
+ List extensions in the resolved project.
+
+ Args:
+ limit: Limit the number of extensions to return.
+
+ name: Exact-match filter on extension name using the database collation. In
+ production, matching is case- and accent-insensitive. During the default-project
+ migration, unscoped requests prefer a concrete default-project extension over a
+ legacy unscoped extension with the same name.
+
+ offset: Offset the number of extensions to return.
+
+ query: Case-insensitive substring match against extension name. IDs match by exact
+ value.
+
+ 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(
"/extensions",
+ page=AsyncOffsetPagination[ExtensionListResponse],
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ extension_list_params.ExtensionListParams,
+ ),
),
- cast_to=ExtensionListResponse,
+ model=ExtensionListResponse,
)
async def delete(
@@ -401,6 +517,40 @@ async def download_from_chrome_store(
cast_to=AsyncBinaryAPIResponse,
)
+ async def get(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ExtensionGetResponse:
+ """
+ Get an extension's metadata (name, size, timestamps) by ID or name, without
+ downloading the archive.
+
+ 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_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("/extensions/{id_or_name}/metadata", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ExtensionGetResponse,
+ )
+
async def upload(
self,
*,
@@ -472,6 +622,9 @@ def __init__(self, extensions: ExtensionsResource) -> None:
extensions.download_from_chrome_store,
BinaryAPIResponse,
)
+ self.get = to_raw_response_wrapper(
+ extensions.get,
+ )
self.upload = to_raw_response_wrapper(
extensions.upload,
)
@@ -495,6 +648,9 @@ def __init__(self, extensions: AsyncExtensionsResource) -> None:
extensions.download_from_chrome_store,
AsyncBinaryAPIResponse,
)
+ self.get = async_to_raw_response_wrapper(
+ extensions.get,
+ )
self.upload = async_to_raw_response_wrapper(
extensions.upload,
)
@@ -518,6 +674,9 @@ def __init__(self, extensions: ExtensionsResource) -> None:
extensions.download_from_chrome_store,
StreamedBinaryAPIResponse,
)
+ self.get = to_streamed_response_wrapper(
+ extensions.get,
+ )
self.upload = to_streamed_response_wrapper(
extensions.upload,
)
@@ -541,6 +700,9 @@ def __init__(self, extensions: AsyncExtensionsResource) -> None:
extensions.download_from_chrome_store,
AsyncStreamedBinaryAPIResponse,
)
+ self.get = async_to_streamed_response_wrapper(
+ extensions.get,
+ )
self.upload = async_to_streamed_response_wrapper(
extensions.upload,
)
diff --git a/src/kernel/resources/invocations.py b/src/kernel/resources/invocations.py
index 6d367e9e..30db7dac 100644
--- a/src/kernel/resources/invocations.py
+++ b/src/kernel/resources/invocations.py
@@ -203,6 +203,7 @@ def list(
deployment_id: str | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
+ query: str | Omit = omit,
since: str | Omit = omit,
status: Literal["queued", "running", "succeeded", "failed"] | Omit = omit,
version: str | Omit = omit,
@@ -229,6 +230,8 @@ def list(
offset: Offset the number of invocations to return.
+ query: Search invocations by ID, app name, or action name.
+
since: Show invocations that have started since the given time (RFC timestamps or
durations like 5m).
@@ -259,6 +262,7 @@ def list(
"deployment_id": deployment_id,
"limit": limit,
"offset": offset,
+ "query": query,
"since": since,
"status": status,
"version": version,
@@ -556,6 +560,7 @@ def list(
deployment_id: str | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
+ query: str | Omit = omit,
since: str | Omit = omit,
status: Literal["queued", "running", "succeeded", "failed"] | Omit = omit,
version: str | Omit = omit,
@@ -582,6 +587,8 @@ def list(
offset: Offset the number of invocations to return.
+ query: Search invocations by ID, app name, or action name.
+
since: Show invocations that have started since the given time (RFC timestamps or
durations like 5m).
@@ -612,6 +619,7 @@ def list(
"deployment_id": deployment_id,
"limit": limit,
"offset": offset,
+ "query": query,
"since": since,
"status": status,
"version": version,
diff --git a/src/kernel/resources/organization/__init__.py b/src/kernel/resources/organization/__init__.py
new file mode 100644
index 00000000..ae6da711
--- /dev/null
+++ b/src/kernel/resources/organization/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .limits import (
+ LimitsResource,
+ AsyncLimitsResource,
+ LimitsResourceWithRawResponse,
+ AsyncLimitsResourceWithRawResponse,
+ LimitsResourceWithStreamingResponse,
+ AsyncLimitsResourceWithStreamingResponse,
+)
+from .entitlements import (
+ EntitlementsResource,
+ AsyncEntitlementsResource,
+ EntitlementsResourceWithRawResponse,
+ AsyncEntitlementsResourceWithRawResponse,
+ EntitlementsResourceWithStreamingResponse,
+ AsyncEntitlementsResourceWithStreamingResponse,
+)
+from .organization import (
+ OrganizationResource,
+ AsyncOrganizationResource,
+ OrganizationResourceWithRawResponse,
+ AsyncOrganizationResourceWithRawResponse,
+ OrganizationResourceWithStreamingResponse,
+ AsyncOrganizationResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "EntitlementsResource",
+ "AsyncEntitlementsResource",
+ "EntitlementsResourceWithRawResponse",
+ "AsyncEntitlementsResourceWithRawResponse",
+ "EntitlementsResourceWithStreamingResponse",
+ "AsyncEntitlementsResourceWithStreamingResponse",
+ "LimitsResource",
+ "AsyncLimitsResource",
+ "LimitsResourceWithRawResponse",
+ "AsyncLimitsResourceWithRawResponse",
+ "LimitsResourceWithStreamingResponse",
+ "AsyncLimitsResourceWithStreamingResponse",
+ "OrganizationResource",
+ "AsyncOrganizationResource",
+ "OrganizationResourceWithRawResponse",
+ "AsyncOrganizationResourceWithRawResponse",
+ "OrganizationResourceWithStreamingResponse",
+ "AsyncOrganizationResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/organization/entitlements.py b/src/kernel/resources/organization/entitlements.py
new file mode 100644
index 00000000..05dc90f8
--- /dev/null
+++ b/src/kernel/resources/organization/entitlements.py
@@ -0,0 +1,147 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+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.organization.org_entitlements import OrgEntitlements
+
+__all__ = ["EntitlementsResource", "AsyncEntitlementsResource"]
+
+
+class EntitlementsResource(SyncAPIResource):
+ """Read and manage organization-level limits."""
+
+ @cached_property
+ def with_raw_response(self) -> EntitlementsResourceWithRawResponse:
+ """
+ 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 EntitlementsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> EntitlementsResourceWithStreamingResponse:
+ """
+ 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 EntitlementsResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OrgEntitlements:
+ """
+ Get the authenticated organization's effective feature access and constraints
+ after applying its plan, active trial treatment, plan status, and
+ organization-specific overrides. Null constraint values mean unlimited.
+ """
+ return self._get(
+ "/org/entitlements",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OrgEntitlements,
+ )
+
+
+class AsyncEntitlementsResource(AsyncAPIResource):
+ """Read and manage organization-level limits."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncEntitlementsResourceWithRawResponse:
+ """
+ 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 AsyncEntitlementsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncEntitlementsResourceWithStreamingResponse:
+ """
+ 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 AsyncEntitlementsResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OrgEntitlements:
+ """
+ Get the authenticated organization's effective feature access and constraints
+ after applying its plan, active trial treatment, plan status, and
+ organization-specific overrides. Null constraint values mean unlimited.
+ """
+ return await self._get(
+ "/org/entitlements",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OrgEntitlements,
+ )
+
+
+class EntitlementsResourceWithRawResponse:
+ def __init__(self, entitlements: EntitlementsResource) -> None:
+ self._entitlements = entitlements
+
+ self.retrieve = to_raw_response_wrapper(
+ entitlements.retrieve,
+ )
+
+
+class AsyncEntitlementsResourceWithRawResponse:
+ def __init__(self, entitlements: AsyncEntitlementsResource) -> None:
+ self._entitlements = entitlements
+
+ self.retrieve = async_to_raw_response_wrapper(
+ entitlements.retrieve,
+ )
+
+
+class EntitlementsResourceWithStreamingResponse:
+ def __init__(self, entitlements: EntitlementsResource) -> None:
+ self._entitlements = entitlements
+
+ self.retrieve = to_streamed_response_wrapper(
+ entitlements.retrieve,
+ )
+
+
+class AsyncEntitlementsResourceWithStreamingResponse:
+ def __init__(self, entitlements: AsyncEntitlementsResource) -> None:
+ self._entitlements = entitlements
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ entitlements.retrieve,
+ )
diff --git a/src/kernel/resources/organization/limits.py b/src/kernel/resources/organization/limits.py
new file mode 100644
index 00000000..5654164b
--- /dev/null
+++ b/src/kernel/resources/organization/limits.py
@@ -0,0 +1,243 @@
+# 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 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.organization import limit_update_params
+from ...types.organization.org_limits import OrgLimits
+
+__all__ = ["LimitsResource", "AsyncLimitsResource"]
+
+
+class LimitsResource(SyncAPIResource):
+ """Read and manage organization-level limits."""
+
+ @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,
+ *,
+ # 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,
+ ) -> OrgLimits:
+ """
+ Get the organization's effective limits and current concurrency, managed auth,
+ and vault usage.
+ """
+ return self._get(
+ "/org/limits",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OrgLimits,
+ )
+
+ def update(
+ self,
+ *,
+ default_project_max_concurrent_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,
+ ) -> OrgLimits:
+ """
+ Set the default per-project concurrency cap applied to projects without an
+ explicit override. Set the value to 0 to remove the default; omit to leave it
+ unchanged. The default cannot exceed the organization's concurrency limit.
+
+ Args:
+ default_project_max_concurrent_sessions: Default maximum concurrent browsers for projects without an explicit override.
+ Set to 0 to remove the default; omit to leave unchanged. Cannot exceed the
+ organization's concurrency limit.
+
+ 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._patch(
+ "/org/limits",
+ body=maybe_transform(
+ {"default_project_max_concurrent_sessions": default_project_max_concurrent_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=OrgLimits,
+ )
+
+
+class AsyncLimitsResource(AsyncAPIResource):
+ """Read and manage organization-level limits."""
+
+ @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,
+ *,
+ # 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,
+ ) -> OrgLimits:
+ """
+ Get the organization's effective limits and current concurrency, managed auth,
+ and vault usage.
+ """
+ return await self._get(
+ "/org/limits",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OrgLimits,
+ )
+
+ async def update(
+ self,
+ *,
+ default_project_max_concurrent_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,
+ ) -> OrgLimits:
+ """
+ Set the default per-project concurrency cap applied to projects without an
+ explicit override. Set the value to 0 to remove the default; omit to leave it
+ unchanged. The default cannot exceed the organization's concurrency limit.
+
+ Args:
+ default_project_max_concurrent_sessions: Default maximum concurrent browsers for projects without an explicit override.
+ Set to 0 to remove the default; omit to leave unchanged. Cannot exceed the
+ organization's concurrency limit.
+
+ 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._patch(
+ "/org/limits",
+ body=await async_maybe_transform(
+ {"default_project_max_concurrent_sessions": default_project_max_concurrent_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=OrgLimits,
+ )
+
+
+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/organization/organization.py b/src/kernel/resources/organization/organization.py
new file mode 100644
index 00000000..78ad5624
--- /dev/null
+++ b/src/kernel/resources/organization/organization.py
@@ -0,0 +1,146 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .limits import (
+ LimitsResource,
+ AsyncLimitsResource,
+ LimitsResourceWithRawResponse,
+ AsyncLimitsResourceWithRawResponse,
+ LimitsResourceWithStreamingResponse,
+ AsyncLimitsResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from .entitlements import (
+ EntitlementsResource,
+ AsyncEntitlementsResource,
+ EntitlementsResourceWithRawResponse,
+ AsyncEntitlementsResourceWithRawResponse,
+ EntitlementsResourceWithStreamingResponse,
+ AsyncEntitlementsResourceWithStreamingResponse,
+)
+
+__all__ = ["OrganizationResource", "AsyncOrganizationResource"]
+
+
+class OrganizationResource(SyncAPIResource):
+ @cached_property
+ def entitlements(self) -> EntitlementsResource:
+ """Read and manage organization-level limits."""
+ return EntitlementsResource(self._client)
+
+ @cached_property
+ def limits(self) -> LimitsResource:
+ """Read and manage organization-level limits."""
+ return LimitsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> OrganizationResourceWithRawResponse:
+ """
+ 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 OrganizationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> OrganizationResourceWithStreamingResponse:
+ """
+ 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 OrganizationResourceWithStreamingResponse(self)
+
+
+class AsyncOrganizationResource(AsyncAPIResource):
+ @cached_property
+ def entitlements(self) -> AsyncEntitlementsResource:
+ """Read and manage organization-level limits."""
+ return AsyncEntitlementsResource(self._client)
+
+ @cached_property
+ def limits(self) -> AsyncLimitsResource:
+ """Read and manage organization-level limits."""
+ return AsyncLimitsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncOrganizationResourceWithRawResponse:
+ """
+ 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 AsyncOrganizationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncOrganizationResourceWithStreamingResponse:
+ """
+ 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 AsyncOrganizationResourceWithStreamingResponse(self)
+
+
+class OrganizationResourceWithRawResponse:
+ def __init__(self, organization: OrganizationResource) -> None:
+ self._organization = organization
+
+ @cached_property
+ def entitlements(self) -> EntitlementsResourceWithRawResponse:
+ """Read and manage organization-level limits."""
+ return EntitlementsResourceWithRawResponse(self._organization.entitlements)
+
+ @cached_property
+ def limits(self) -> LimitsResourceWithRawResponse:
+ """Read and manage organization-level limits."""
+ return LimitsResourceWithRawResponse(self._organization.limits)
+
+
+class AsyncOrganizationResourceWithRawResponse:
+ def __init__(self, organization: AsyncOrganizationResource) -> None:
+ self._organization = organization
+
+ @cached_property
+ def entitlements(self) -> AsyncEntitlementsResourceWithRawResponse:
+ """Read and manage organization-level limits."""
+ return AsyncEntitlementsResourceWithRawResponse(self._organization.entitlements)
+
+ @cached_property
+ def limits(self) -> AsyncLimitsResourceWithRawResponse:
+ """Read and manage organization-level limits."""
+ return AsyncLimitsResourceWithRawResponse(self._organization.limits)
+
+
+class OrganizationResourceWithStreamingResponse:
+ def __init__(self, organization: OrganizationResource) -> None:
+ self._organization = organization
+
+ @cached_property
+ def entitlements(self) -> EntitlementsResourceWithStreamingResponse:
+ """Read and manage organization-level limits."""
+ return EntitlementsResourceWithStreamingResponse(self._organization.entitlements)
+
+ @cached_property
+ def limits(self) -> LimitsResourceWithStreamingResponse:
+ """Read and manage organization-level limits."""
+ return LimitsResourceWithStreamingResponse(self._organization.limits)
+
+
+class AsyncOrganizationResourceWithStreamingResponse:
+ def __init__(self, organization: AsyncOrganizationResource) -> None:
+ self._organization = organization
+
+ @cached_property
+ def entitlements(self) -> AsyncEntitlementsResourceWithStreamingResponse:
+ """Read and manage organization-level limits."""
+ return AsyncEntitlementsResourceWithStreamingResponse(self._organization.entitlements)
+
+ @cached_property
+ def limits(self) -> AsyncLimitsResourceWithStreamingResponse:
+ """Read and manage organization-level limits."""
+ return AsyncLimitsResourceWithStreamingResponse(self._organization.limits)
diff --git a/src/kernel/resources/profiles.py b/src/kernel/resources/profiles.py
index 4083f12d..8642940c 100644
--- a/src/kernel/resources/profiles.py
+++ b/src/kernel/resources/profiles.py
@@ -2,9 +2,11 @@
from __future__ import annotations
+from typing_extensions import Literal
+
import httpx
-from ..types import profile_list_params, profile_create_params
+from ..types import profile_list_params, profile_create_params, profile_update_params, profile_download_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
@@ -68,7 +70,9 @@ def create(
sessions.
Args:
- name: Optional name of the profile. Must be unique within the project.
+ name: Optional name of the profile. Must be unique within the logical project; during
+ the default-project migration, unscoped profiles and profiles in the org default
+ project are treated as the same project.
extra_headers: Send extra headers
@@ -120,10 +124,57 @@ def retrieve(
cast_to=Profile,
)
+ def update(
+ self,
+ 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.
+ # 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,
+ ) -> Profile:
+ """Update a profile's name.
+
+ Names must be unique within the logical project; during
+ the default-project migration, unscoped profiles and profiles in the org default
+ project are treated as the same project. Duplicate-name conflicts are checked
+ before update but are best-effort because there is no backing unique index.
+ Renaming a profile while a browser session references it by name may prevent
+ that session's changes from saving; prefer renaming when the profile is not in
+ use.
+
+ Args:
+ name: New profile name. Must be unique within the logical project; during the
+ default-project migration, unscoped profiles and profiles in the org default
+ project are treated as the same project.
+
+ 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_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("/profiles/{id_or_name}", id_or_name=id_or_name),
+ body=maybe_transform({"name": name}, profile_update_params.ProfileUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Profile,
+ )
+
def list(
self,
*,
limit: int | Omit = omit,
+ name: str | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -139,9 +190,14 @@ def list(
Args:
limit: Limit the number of profiles to return.
+ name: Exact-match filter on profile name using the database collation. In production,
+ matching is case- and accent-insensitive. During the default-project migration,
+ unscoped requests prefer a concrete default-project profile over a legacy
+ unscoped profile with the same name.
+
offset: Offset the number of profiles to return.
- query: Search profiles by name or ID.
+ query: Case-insensitive substring match against profile name or ID.
extra_headers: Send extra headers
@@ -162,6 +218,7 @@ def list(
query=maybe_transform(
{
"limit": limit,
+ "name": name,
"offset": offset,
"query": query,
},
@@ -209,6 +266,7 @@ def download(
self,
id_or_name: str,
*,
+ format: Literal["tar.zst", "tar"] | 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,
@@ -216,10 +274,17 @@ def download(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BinaryAPIResponse:
- """
- Returns a zstd-compressed tar file of the full user-data directory.
+ """Downloads the profile in its stored format by default.
+
+ Current profiles are
+ returned as zstd-compressed tar archives, while legacy profiles remain JSON. Set
+ `format=tar` to decompress current profiles during download; legacy profiles
+ remain JSON.
Args:
+ format: Response format for current profile archives. Legacy profiles are always
+ returned as JSON.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -234,7 +299,11 @@ def download(
return self._get(
path_template("/profiles/{id_or_name}/download", id_or_name=id_or_name),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"format": format}, profile_download_params.ProfileDownloadParams),
),
cast_to=BinaryAPIResponse,
)
@@ -278,7 +347,9 @@ async def create(
sessions.
Args:
- name: Optional name of the profile. Must be unique within the project.
+ name: Optional name of the profile. Must be unique within the logical project; during
+ the default-project migration, unscoped profiles and profiles in the org default
+ project are treated as the same project.
extra_headers: Send extra headers
@@ -330,10 +401,57 @@ async def retrieve(
cast_to=Profile,
)
+ async def update(
+ self,
+ 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.
+ # 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,
+ ) -> Profile:
+ """Update a profile's name.
+
+ Names must be unique within the logical project; during
+ the default-project migration, unscoped profiles and profiles in the org default
+ project are treated as the same project. Duplicate-name conflicts are checked
+ before update but are best-effort because there is no backing unique index.
+ Renaming a profile while a browser session references it by name may prevent
+ that session's changes from saving; prefer renaming when the profile is not in
+ use.
+
+ Args:
+ name: New profile name. Must be unique within the logical project; during the
+ default-project migration, unscoped profiles and profiles in the org default
+ project are treated as the same project.
+
+ 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_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("/profiles/{id_or_name}", id_or_name=id_or_name),
+ body=await async_maybe_transform({"name": name}, profile_update_params.ProfileUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Profile,
+ )
+
def list(
self,
*,
limit: int | Omit = omit,
+ name: str | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -349,9 +467,14 @@ def list(
Args:
limit: Limit the number of profiles to return.
+ name: Exact-match filter on profile name using the database collation. In production,
+ matching is case- and accent-insensitive. During the default-project migration,
+ unscoped requests prefer a concrete default-project profile over a legacy
+ unscoped profile with the same name.
+
offset: Offset the number of profiles to return.
- query: Search profiles by name or ID.
+ query: Case-insensitive substring match against profile name or ID.
extra_headers: Send extra headers
@@ -372,6 +495,7 @@ def list(
query=maybe_transform(
{
"limit": limit,
+ "name": name,
"offset": offset,
"query": query,
},
@@ -419,6 +543,7 @@ async def download(
self,
id_or_name: str,
*,
+ format: Literal["tar.zst", "tar"] | 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,
@@ -426,10 +551,17 @@ async def download(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AsyncBinaryAPIResponse:
- """
- Returns a zstd-compressed tar file of the full user-data directory.
+ """Downloads the profile in its stored format by default.
+
+ Current profiles are
+ returned as zstd-compressed tar archives, while legacy profiles remain JSON. Set
+ `format=tar` to decompress current profiles during download; legacy profiles
+ remain JSON.
Args:
+ format: Response format for current profile archives. Legacy profiles are always
+ returned as JSON.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -444,7 +576,11 @@ async def download(
return await self._get(
path_template("/profiles/{id_or_name}/download", id_or_name=id_or_name),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"format": format}, profile_download_params.ProfileDownloadParams),
),
cast_to=AsyncBinaryAPIResponse,
)
@@ -460,6 +596,9 @@ def __init__(self, profiles: ProfilesResource) -> None:
self.retrieve = to_raw_response_wrapper(
profiles.retrieve,
)
+ self.update = to_raw_response_wrapper(
+ profiles.update,
+ )
self.list = to_raw_response_wrapper(
profiles.list,
)
@@ -482,6 +621,9 @@ def __init__(self, profiles: AsyncProfilesResource) -> None:
self.retrieve = async_to_raw_response_wrapper(
profiles.retrieve,
)
+ self.update = async_to_raw_response_wrapper(
+ profiles.update,
+ )
self.list = async_to_raw_response_wrapper(
profiles.list,
)
@@ -504,6 +646,9 @@ def __init__(self, profiles: ProfilesResource) -> None:
self.retrieve = to_streamed_response_wrapper(
profiles.retrieve,
)
+ self.update = to_streamed_response_wrapper(
+ profiles.update,
+ )
self.list = to_streamed_response_wrapper(
profiles.list,
)
@@ -526,6 +671,9 @@ def __init__(self, profiles: AsyncProfilesResource) -> None:
self.retrieve = async_to_streamed_response_wrapper(
profiles.retrieve,
)
+ self.update = async_to_streamed_response_wrapper(
+ profiles.update,
+ )
self.list = async_to_streamed_response_wrapper(
profiles.list,
)
diff --git a/src/kernel/resources/projects/limits.py b/src/kernel/resources/projects/limits.py
index eeff5930..ef95b4d0 100644
--- a/src/kernel/resources/projects/limits.py
+++ b/src/kernel/resources/projects/limits.py
@@ -24,7 +24,11 @@
class LimitsResource(SyncAPIResource):
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
@cached_property
def with_raw_response(self) -> LimitsResourceWithRawResponse:
@@ -47,7 +51,7 @@ def with_streaming_response(self) -> LimitsResourceWithStreamingResponse:
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.
@@ -70,10 +74,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("/projects/{id}/limits", id=id),
+ path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -82,11 +86,10 @@ def retrieve(
def update(
self,
- id: str,
+ id_or_name: 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.
@@ -105,14 +108,12 @@ def update(
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_concurrent_sessions: Maximum concurrent browsers for this project, covering both on-demand sessions
+ and browser pool reservations. 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.
+ max_pooled_sessions: Deprecated: pooled browsers now count toward `max_concurrent_sessions`. Requests
+ that set this field are rejected with a 400.
extra_headers: Send extra headers
@@ -122,15 +123,14 @@ 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("/projects/{id}/limits", id=id),
+ path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name),
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,
@@ -143,7 +143,11 @@ def update(
class AsyncLimitsResource(AsyncAPIResource):
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
@cached_property
def with_raw_response(self) -> AsyncLimitsResourceWithRawResponse:
@@ -166,7 +170,7 @@ def with_streaming_response(self) -> AsyncLimitsResourceWithStreamingResponse:
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.
@@ -189,10 +193,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("/projects/{id}/limits", id=id),
+ path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -201,11 +205,10 @@ async def retrieve(
async def update(
self,
- id: str,
+ id_or_name: 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.
@@ -224,14 +227,12 @@ async def update(
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_concurrent_sessions: Maximum concurrent browsers for this project, covering both on-demand sessions
+ and browser pool reservations. 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.
+ max_pooled_sessions: Deprecated: pooled browsers now count toward `max_concurrent_sessions`. Requests
+ that set this field are rejected with a 400.
extra_headers: Send extra headers
@@ -241,15 +242,14 @@ 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("/projects/{id}/limits", id=id),
+ path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name),
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,
diff --git a/src/kernel/resources/projects/projects.py b/src/kernel/resources/projects/projects.py
index f73e70d7..ca519a75 100644
--- a/src/kernel/resources/projects/projects.py
+++ b/src/kernel/resources/projects/projects.py
@@ -33,11 +33,19 @@
class ProjectsResource(SyncAPIResource):
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
@cached_property
def limits(self) -> LimitsResource:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
return LimitsResource(self._client)
@cached_property
@@ -70,13 +78,11 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Project:
- """Create a new project within the authenticated organization.
-
- Requires the
- projects feature flag.
+ """
+ Create a new project within the authenticated organization.
Args:
- name: Project name (1-255 characters)
+ name: Project name (1-255 Unicode code points; cannot contain `/` or `%`)
extra_headers: Send extra headers
@@ -87,7 +93,7 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
return self._post(
- "/projects",
+ "/org/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
@@ -97,7 +103,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.
@@ -106,8 +112,9 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Project:
- """
- Get a project by ID.
+ """Get a project by its ID or by its name.
+
+ Names are unique within an organization.
Args:
extra_headers: Send extra headers
@@ -118,10 +125,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("/projects/{id}", id=id),
+ path_template("/org/projects/{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
),
@@ -130,7 +137,7 @@ def retrieve(
def update(
self,
- id: str,
+ id_or_name: str,
*,
name: str | Omit = omit,
status: Literal["active", "archived"] | Omit = omit,
@@ -145,7 +152,7 @@ def update(
Update a project's name or status.
Args:
- name: New project name
+ name: New project name (1-255 Unicode code points; cannot contain `/` or `%`)
status: New project status
@@ -157,10 +164,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("/projects/{id}", id=id),
+ path_template("/org/projects/{id_or_name}", id_or_name=id_or_name),
body=maybe_transform(
{
"name": name,
@@ -178,6 +185,7 @@ def list(
self,
*,
limit: int | Omit = omit,
+ name: str | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -193,6 +201,9 @@ def list(
Args:
limit: Maximum number of results to return
+ name: Exact-match filter on project name using the database collation. In production,
+ matching is case- and accent-insensitive.
+
offset: Number of results to skip
query: Case-insensitive substring match against project name
@@ -206,7 +217,7 @@ def list(
timeout: Override the client-level default timeout for this request, in seconds
"""
return self._get_api_list(
- "/projects",
+ "/org/projects",
page=SyncOffsetPagination[Project],
options=make_request_options(
extra_headers=extra_headers,
@@ -216,6 +227,7 @@ def list(
query=maybe_transform(
{
"limit": limit,
+ "name": name,
"offset": offset,
"query": query,
},
@@ -227,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.
@@ -249,11 +261,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("/projects/{id}", id=id),
+ path_template("/org/projects/{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
),
@@ -262,11 +274,19 @@ def delete(
class AsyncProjectsResource(AsyncAPIResource):
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
@cached_property
def limits(self) -> AsyncLimitsResource:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
return AsyncLimitsResource(self._client)
@cached_property
@@ -299,13 +319,11 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Project:
- """Create a new project within the authenticated organization.
-
- Requires the
- projects feature flag.
+ """
+ Create a new project within the authenticated organization.
Args:
- name: Project name (1-255 characters)
+ name: Project name (1-255 Unicode code points; cannot contain `/` or `%`)
extra_headers: Send extra headers
@@ -316,7 +334,7 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
return await self._post(
- "/projects",
+ "/org/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
@@ -326,7 +344,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.
@@ -335,8 +353,9 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Project:
- """
- Get a project by ID.
+ """Get a project by its ID or by its name.
+
+ Names are unique within an organization.
Args:
extra_headers: Send extra headers
@@ -347,10 +366,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("/projects/{id}", id=id),
+ path_template("/org/projects/{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
),
@@ -359,7 +378,7 @@ async def retrieve(
async def update(
self,
- id: str,
+ id_or_name: str,
*,
name: str | Omit = omit,
status: Literal["active", "archived"] | Omit = omit,
@@ -374,7 +393,7 @@ async def update(
Update a project's name or status.
Args:
- name: New project name
+ name: New project name (1-255 Unicode code points; cannot contain `/` or `%`)
status: New project status
@@ -386,10 +405,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("/projects/{id}", id=id),
+ path_template("/org/projects/{id_or_name}", id_or_name=id_or_name),
body=await async_maybe_transform(
{
"name": name,
@@ -407,6 +426,7 @@ def list(
self,
*,
limit: int | Omit = omit,
+ name: str | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -422,6 +442,9 @@ def list(
Args:
limit: Maximum number of results to return
+ name: Exact-match filter on project name using the database collation. In production,
+ matching is case- and accent-insensitive.
+
offset: Number of results to skip
query: Case-insensitive substring match against project name
@@ -435,7 +458,7 @@ def list(
timeout: Override the client-level default timeout for this request, in seconds
"""
return self._get_api_list(
- "/projects",
+ "/org/projects",
page=AsyncOffsetPagination[Project],
options=make_request_options(
extra_headers=extra_headers,
@@ -445,6 +468,7 @@ def list(
query=maybe_transform(
{
"limit": limit,
+ "name": name,
"offset": offset,
"query": query,
},
@@ -456,7 +480,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.
@@ -478,11 +502,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("/projects/{id}", id=id),
+ path_template("/org/projects/{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
),
@@ -512,7 +536,11 @@ def __init__(self, projects: ProjectsResource) -> None:
@cached_property
def limits(self) -> LimitsResourceWithRawResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
return LimitsResourceWithRawResponse(self._projects.limits)
@@ -538,7 +566,11 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
@cached_property
def limits(self) -> AsyncLimitsResourceWithRawResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
return AsyncLimitsResourceWithRawResponse(self._projects.limits)
@@ -564,7 +596,11 @@ def __init__(self, projects: ProjectsResource) -> None:
@cached_property
def limits(self) -> LimitsResourceWithStreamingResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
return LimitsResourceWithStreamingResponse(self._projects.limits)
@@ -590,5 +626,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None:
@cached_property
def limits(self) -> AsyncLimitsResourceWithStreamingResponse:
- """Create and manage projects for resource isolation within an organization."""
+ """
+ Create and manage projects for resource isolation within an organization.
+ When projects are disabled for the organization, project operations return
+ `404` with code `projects_disabled`.
+ """
return AsyncLimitsResourceWithStreamingResponse(self._projects.limits)
diff --git a/src/kernel/resources/proxies.py b/src/kernel/resources/proxies.py
index bacdd570..4b0d3458 100644
--- a/src/kernel/resources/proxies.py
+++ b/src/kernel/resources/proxies.py
@@ -6,7 +6,7 @@
import httpx
-from ..types import proxy_check_params, proxy_create_params
+from ..types import proxy_list_params, proxy_check_params, proxy_create_params, proxy_update_params
from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
@@ -17,10 +17,12 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from .._base_client import make_request_options
+from ..pagination import SyncOffsetPagination, AsyncOffsetPagination
+from .._base_client import AsyncPaginator, make_request_options
from ..types.proxy_list_response import ProxyListResponse
from ..types.proxy_check_response import ProxyCheckResponse
from ..types.proxy_create_response import ProxyCreateResponse
+from ..types.proxy_update_response import ProxyUpdateResponse
from ..types.proxy_retrieve_response import ProxyRetrieveResponse
__all__ = ["ProxiesResource", "AsyncProxiesResource"]
@@ -51,7 +53,7 @@ def with_streaming_response(self) -> ProxiesResourceWithStreamingResponse:
def create(
self,
*,
- type: Literal["datacenter", "isp", "residential", "mobile", "custom"],
+ type: Literal["isp", "residential", "mobile", "custom"],
bypass_hosts: SequenceNotStr[str] | Omit = omit,
config: proxy_create_params.Config | Omit = omit,
name: str | Omit = omit,
@@ -64,15 +66,12 @@ def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProxyCreateResponse:
"""
- Create a new proxy configuration for the caller's organization.
+ Create a new proxy configuration in the resolved project.
Args:
- type: Proxy type to use. In terms of quality for avoiding bot-detection, from best to
- worst: `mobile` > `residential` > `isp` > `datacenter`.
-
bypass_hosts: Hostnames that should bypass the parent proxy and connect directly.
- config: Configuration specific to the selected proxy `type`.
+ config: Configuration for an ISP proxy.
name: Readable name of the proxy.
@@ -106,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.
@@ -116,7 +115,10 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProxyRetrieveResponse:
"""
- Retrieve a proxy belonging to the caller's organization 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
@@ -127,38 +129,117 @@ 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
),
cast_to=ProxyRetrieveResponse,
)
+ def update(
+ self,
+ 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.
+ # 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,
+ ) -> ProxyUpdateResponse:
+ """Update a proxy's name.
+
+ 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
+ allowed because proxies are updated by ID, not by name.
+
+ 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_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_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
+ ),
+ cast_to=ProxyUpdateResponse,
+ )
+
def list(
self,
*,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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,
- ) -> ProxyListResponse:
- """List proxies owned by the caller's organization."""
- return self._get(
+ ) -> SyncOffsetPagination[ProxyListResponse]:
+ """
+ List proxies in the resolved project.
+
+ Args:
+ limit: Limit the number of proxies to return.
+
+ name: Exact-match filter on proxy name using the database collation. In production,
+ matching is case- and accent-insensitive. Names are not required to be unique,
+ so multiple proxies may match.
+
+ offset: Offset the number of proxies to return.
+
+ query: Case-insensitive substring match against proxy name, host, or IP address. IDs
+ match by exact value.
+
+ 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(
"/proxies",
+ page=SyncOffsetPagination[ProxyListResponse],
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ proxy_list_params.ProxyListParams,
+ ),
),
- cast_to=ProxyListResponse,
+ model=ProxyListResponse,
)
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.
@@ -169,7 +250,10 @@ def delete(
) -> None:
"""Soft delete a proxy.
- Sessions referencing it are not modified.
+ Session records referencing it are not modified. If egress
+ binding polling is enabled, existing tunnels for active sessions using the proxy
+ are terminated within one polling interval; subsequent connections through the
+ deleted proxy are rejected.
Args:
extra_headers: Send extra headers
@@ -180,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
),
@@ -193,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.
@@ -206,23 +290,25 @@ def check(
"""Run a health check on the proxy to verify it's working.
Optionally specify a URL
- to test reachability against a specific target. For ISP and datacenter proxies,
- this reliably tests whether the target site is reachable from the proxy's stable
- exit IP. For residential and mobile proxies, the exit node varies between
- requests, so this validates proxy configuration and connectivity rather than
- guaranteeing site-specific reachability.
+ to test reachability against a specific target. For ISP proxies, this reliably
+ tests whether the target site is reachable from the proxy's stable exit IP. For
+ residential and mobile proxies, the exit node varies between requests, so this
+ validates proxy configuration and connectivity rather than guaranteeing
+ site-specific reachability.
Args:
url: An optional URL to test reachability against. If provided, the proxy check will
test connectivity to this URL instead of the default test URLs. Only HTTP and
HTTPS schemes are allowed, and the URL must resolve to a public IP address. For
- ISP and datacenter proxies, the exit IP is stable, so a successful check
- reliably indicates that subsequent browser sessions will reach the target site
- with the same IP. For residential and mobile proxies, the exit node changes
- between requests, so a successful check validates proxy configuration but does
- not guarantee that a subsequent browser session will use the same exit IP or
- reach the same site — it is useful for verifying credentials and connectivity,
- not for predicting site-specific behavior.
+ ISP proxies, the exit IP is stable, so a successful check reliably indicates
+ that subsequent browser sessions will reach the target site with the same IP.
+ For residential and mobile proxies, the exit node changes between requests, so a
+ successful check validates proxy configuration but does not guarantee that a
+ subsequent browser session will use the same exit IP or reach the same site — it
+ is useful for verifying credentials and connectivity, not for predicting
+ site-specific behavior. When provided, the check result does not update the
+ proxy's health status, since a failure may indicate a problem with the target
+ site rather than the proxy itself.
extra_headers: Send extra headers
@@ -232,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
@@ -269,7 +355,7 @@ def with_streaming_response(self) -> AsyncProxiesResourceWithStreamingResponse:
async def create(
self,
*,
- type: Literal["datacenter", "isp", "residential", "mobile", "custom"],
+ type: Literal["isp", "residential", "mobile", "custom"],
bypass_hosts: SequenceNotStr[str] | Omit = omit,
config: proxy_create_params.Config | Omit = omit,
name: str | Omit = omit,
@@ -282,15 +368,12 @@ async def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProxyCreateResponse:
"""
- Create a new proxy configuration for the caller's organization.
+ Create a new proxy configuration in the resolved project.
Args:
- type: Proxy type to use. In terms of quality for avoiding bot-detection, from best to
- worst: `mobile` > `residential` > `isp` > `datacenter`.
-
bypass_hosts: Hostnames that should bypass the parent proxy and connect directly.
- config: Configuration specific to the selected proxy `type`.
+ config: Configuration for an ISP proxy.
name: Readable name of the proxy.
@@ -324,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.
@@ -334,7 +417,10 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProxyRetrieveResponse:
"""
- Retrieve a proxy belonging to the caller's organization 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
@@ -345,38 +431,117 @@ 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
),
cast_to=ProxyRetrieveResponse,
)
- async def list(
+ async def update(
self,
+ 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.
# 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,
- ) -> ProxyListResponse:
- """List proxies owned by the caller's organization."""
- return await self._get(
- "/proxies",
+ ) -> ProxyUpdateResponse:
+ """Update a proxy's name.
+
+ 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
+ allowed because proxies are updated by ID, not by name.
+
+ 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_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_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
),
- cast_to=ProxyListResponse,
+ cast_to=ProxyUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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[ProxyListResponse, AsyncOffsetPagination[ProxyListResponse]]:
+ """
+ List proxies in the resolved project.
+
+ Args:
+ limit: Limit the number of proxies to return.
+
+ name: Exact-match filter on proxy name using the database collation. In production,
+ matching is case- and accent-insensitive. Names are not required to be unique,
+ so multiple proxies may match.
+
+ offset: Offset the number of proxies to return.
+
+ query: Case-insensitive substring match against proxy name, host, or IP address. IDs
+ match by exact value.
+
+ 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(
+ "/proxies",
+ page=AsyncOffsetPagination[ProxyListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ proxy_list_params.ProxyListParams,
+ ),
+ ),
+ model=ProxyListResponse,
)
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.
@@ -387,7 +552,10 @@ async def delete(
) -> None:
"""Soft delete a proxy.
- Sessions referencing it are not modified.
+ Session records referencing it are not modified. If egress
+ binding polling is enabled, existing tunnels for active sessions using the proxy
+ are terminated within one polling interval; subsequent connections through the
+ deleted proxy are rejected.
Args:
extra_headers: Send extra headers
@@ -398,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
),
@@ -411,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.
@@ -424,23 +592,25 @@ async def check(
"""Run a health check on the proxy to verify it's working.
Optionally specify a URL
- to test reachability against a specific target. For ISP and datacenter proxies,
- this reliably tests whether the target site is reachable from the proxy's stable
- exit IP. For residential and mobile proxies, the exit node varies between
- requests, so this validates proxy configuration and connectivity rather than
- guaranteeing site-specific reachability.
+ to test reachability against a specific target. For ISP proxies, this reliably
+ tests whether the target site is reachable from the proxy's stable exit IP. For
+ residential and mobile proxies, the exit node varies between requests, so this
+ validates proxy configuration and connectivity rather than guaranteeing
+ site-specific reachability.
Args:
url: An optional URL to test reachability against. If provided, the proxy check will
test connectivity to this URL instead of the default test URLs. Only HTTP and
HTTPS schemes are allowed, and the URL must resolve to a public IP address. For
- ISP and datacenter proxies, the exit IP is stable, so a successful check
- reliably indicates that subsequent browser sessions will reach the target site
- with the same IP. For residential and mobile proxies, the exit node changes
- between requests, so a successful check validates proxy configuration but does
- not guarantee that a subsequent browser session will use the same exit IP or
- reach the same site — it is useful for verifying credentials and connectivity,
- not for predicting site-specific behavior.
+ ISP proxies, the exit IP is stable, so a successful check reliably indicates
+ that subsequent browser sessions will reach the target site with the same IP.
+ For residential and mobile proxies, the exit node changes between requests, so a
+ successful check validates proxy configuration but does not guarantee that a
+ subsequent browser session will use the same exit IP or reach the same site — it
+ is useful for verifying credentials and connectivity, not for predicting
+ site-specific behavior. When provided, the check result does not update the
+ proxy's health status, since a failure may indicate a problem with the target
+ site rather than the proxy itself.
extra_headers: Send extra headers
@@ -450,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
@@ -472,6 +642,9 @@ def __init__(self, proxies: ProxiesResource) -> None:
self.retrieve = to_raw_response_wrapper(
proxies.retrieve,
)
+ self.update = to_raw_response_wrapper(
+ proxies.update,
+ )
self.list = to_raw_response_wrapper(
proxies.list,
)
@@ -493,6 +666,9 @@ def __init__(self, proxies: AsyncProxiesResource) -> None:
self.retrieve = async_to_raw_response_wrapper(
proxies.retrieve,
)
+ self.update = async_to_raw_response_wrapper(
+ proxies.update,
+ )
self.list = async_to_raw_response_wrapper(
proxies.list,
)
@@ -514,6 +690,9 @@ def __init__(self, proxies: ProxiesResource) -> None:
self.retrieve = to_streamed_response_wrapper(
proxies.retrieve,
)
+ self.update = to_streamed_response_wrapper(
+ proxies.update,
+ )
self.list = to_streamed_response_wrapper(
proxies.list,
)
@@ -535,6 +714,9 @@ def __init__(self, proxies: AsyncProxiesResource) -> None:
self.retrieve = async_to_streamed_response_wrapper(
proxies.retrieve,
)
+ self.update = async_to_streamed_response_wrapper(
+ proxies.update,
+ )
self.list = async_to_streamed_response_wrapper(
proxies.list,
)
diff --git a/src/kernel/resources/search/__init__.py b/src/kernel/resources/search/__init__.py
new file mode 100644
index 00000000..0ae39afe
--- /dev/null
+++ b/src/kernel/resources/search/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .search import (
+ SearchResource,
+ AsyncSearchResource,
+ SearchResourceWithRawResponse,
+ AsyncSearchResourceWithRawResponse,
+ SearchResourceWithStreamingResponse,
+ AsyncSearchResourceWithStreamingResponse,
+)
+from .contents import (
+ ContentsResource,
+ AsyncContentsResource,
+ ContentsResourceWithRawResponse,
+ AsyncContentsResourceWithRawResponse,
+ ContentsResourceWithStreamingResponse,
+ AsyncContentsResourceWithStreamingResponse,
+)
+from .providers import (
+ ProvidersResource,
+ AsyncProvidersResource,
+ ProvidersResourceWithRawResponse,
+ AsyncProvidersResourceWithRawResponse,
+ ProvidersResourceWithStreamingResponse,
+ AsyncProvidersResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ContentsResource",
+ "AsyncContentsResource",
+ "ContentsResourceWithRawResponse",
+ "AsyncContentsResourceWithRawResponse",
+ "ContentsResourceWithStreamingResponse",
+ "AsyncContentsResourceWithStreamingResponse",
+ "ProvidersResource",
+ "AsyncProvidersResource",
+ "ProvidersResourceWithRawResponse",
+ "AsyncProvidersResourceWithRawResponse",
+ "ProvidersResourceWithStreamingResponse",
+ "AsyncProvidersResourceWithStreamingResponse",
+ "SearchResource",
+ "AsyncSearchResource",
+ "SearchResourceWithRawResponse",
+ "AsyncSearchResourceWithRawResponse",
+ "SearchResourceWithStreamingResponse",
+ "AsyncSearchResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/search/contents.py b/src/kernel/resources/search/contents.py
new file mode 100644
index 00000000..4704eabd
--- /dev/null
+++ b/src/kernel/resources/search/contents.py
@@ -0,0 +1,233 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, 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.search import content_fetch_params
+from ...types.search.response import Response
+
+__all__ = ["ContentsResource", "AsyncContentsResource"]
+
+
+class ContentsResource(SyncAPIResource):
+ """Search the web and retrieve content for selected results."""
+
+ @cached_property
+ def with_raw_response(self) -> ContentsResourceWithRawResponse:
+ """
+ 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 ContentsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ContentsResourceWithStreamingResponse:
+ """
+ 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 ContentsResourceWithStreamingResponse(self)
+
+ def fetch(
+ self,
+ id: str,
+ *,
+ content: content_fetch_params.Content | Omit = omit,
+ limit: int | Omit = omit,
+ result_ids: SequenceNotStr[str] | Omit = omit,
+ timeout_ms: 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,
+ ) -> Response:
+ """Retrieves selected results from a retained search.
+
+ Provide exactly one of
+ result_ids or limit; the latter fetches the top results. Content defaults to
+ source:auto. Responses preserve result_ids order. Unknown result IDs are
+ rejected before retrieval starts. Missing, expired, or inaccessible searches
+ return 404. Once retrieval begins, return one outcome per selected result,
+ including timeout entries for work unfinished at the overall deadline. Browser
+ retrievals run sequentially in result order, so later results may time out when
+ earlier pages are slow. X-Request-Id identifies this request separately from the
+ search resource.
+
+ Args:
+ content: Defaults to source:auto when omitted.
+
+ limit: Maximum number of search results to fetch when result_ids is omitted, starting
+ from rank 1. Mutually exclusive with result_ids.
+
+ result_ids: Kernel-generated IDs from the referenced retained search, in desired response
+ order. They are not provider-standard IDs. Mutually exclusive with limit.
+
+ timeout_ms: Overall deadline across all selected results.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._post(
+ path_template("/search/{id}/contents", id=id),
+ body=maybe_transform(
+ {
+ "content": content,
+ "limit": limit,
+ "result_ids": result_ids,
+ "timeout_ms": timeout_ms,
+ },
+ content_fetch_params.ContentFetchParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Response,
+ )
+
+
+class AsyncContentsResource(AsyncAPIResource):
+ """Search the web and retrieve content for selected results."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncContentsResourceWithRawResponse:
+ """
+ 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 AsyncContentsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncContentsResourceWithStreamingResponse:
+ """
+ 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 AsyncContentsResourceWithStreamingResponse(self)
+
+ async def fetch(
+ self,
+ id: str,
+ *,
+ content: content_fetch_params.Content | Omit = omit,
+ limit: int | Omit = omit,
+ result_ids: SequenceNotStr[str] | Omit = omit,
+ timeout_ms: 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,
+ ) -> Response:
+ """Retrieves selected results from a retained search.
+
+ Provide exactly one of
+ result_ids or limit; the latter fetches the top results. Content defaults to
+ source:auto. Responses preserve result_ids order. Unknown result IDs are
+ rejected before retrieval starts. Missing, expired, or inaccessible searches
+ return 404. Once retrieval begins, return one outcome per selected result,
+ including timeout entries for work unfinished at the overall deadline. Browser
+ retrievals run sequentially in result order, so later results may time out when
+ earlier pages are slow. X-Request-Id identifies this request separately from the
+ search resource.
+
+ Args:
+ content: Defaults to source:auto when omitted.
+
+ limit: Maximum number of search results to fetch when result_ids is omitted, starting
+ from rank 1. Mutually exclusive with result_ids.
+
+ result_ids: Kernel-generated IDs from the referenced retained search, in desired response
+ order. They are not provider-standard IDs. Mutually exclusive with limit.
+
+ timeout_ms: Overall deadline across all selected results.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._post(
+ path_template("/search/{id}/contents", id=id),
+ body=await async_maybe_transform(
+ {
+ "content": content,
+ "limit": limit,
+ "result_ids": result_ids,
+ "timeout_ms": timeout_ms,
+ },
+ content_fetch_params.ContentFetchParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Response,
+ )
+
+
+class ContentsResourceWithRawResponse:
+ def __init__(self, contents: ContentsResource) -> None:
+ self._contents = contents
+
+ self.fetch = to_raw_response_wrapper(
+ contents.fetch,
+ )
+
+
+class AsyncContentsResourceWithRawResponse:
+ def __init__(self, contents: AsyncContentsResource) -> None:
+ self._contents = contents
+
+ self.fetch = async_to_raw_response_wrapper(
+ contents.fetch,
+ )
+
+
+class ContentsResourceWithStreamingResponse:
+ def __init__(self, contents: ContentsResource) -> None:
+ self._contents = contents
+
+ self.fetch = to_streamed_response_wrapper(
+ contents.fetch,
+ )
+
+
+class AsyncContentsResourceWithStreamingResponse:
+ def __init__(self, contents: AsyncContentsResource) -> None:
+ self._contents = contents
+
+ self.fetch = async_to_streamed_response_wrapper(
+ contents.fetch,
+ )
diff --git a/src/kernel/resources/search/providers.py b/src/kernel/resources/search/providers.py
new file mode 100644
index 00000000..c2b3b31e
--- /dev/null
+++ b/src/kernel/resources/search/providers.py
@@ -0,0 +1,189 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.search import provider_list_params
+from ...types.search.provider_list_response import ProviderListResponse
+
+__all__ = ["ProvidersResource", "AsyncProvidersResource"]
+
+
+class ProvidersResource(SyncAPIResource):
+ """Search the web and retrieve content for selected results."""
+
+ @cached_property
+ def with_raw_response(self) -> ProvidersResourceWithRawResponse:
+ """
+ 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 ProvidersResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ProvidersResourceWithStreamingResponse:
+ """
+ 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 ProvidersResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ slug: Literal["brave", "exa", "perplexity", "context", "parallel", "valyu", "octen", "you", "tavily", "serpapi"]
+ | 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,
+ ) -> ProviderListResponse:
+ """Lists providers, capabilities, and machine-readable native-option schemas.
+
+ Auto
+ and fallback are strategies, not provider entries. The list is not paginated and
+ contains no latency benchmarks. X-Request-Id identifies the request.
+
+ Args:
+ slug: Optional concrete provider slug filter. Omit to list every provider. A slug that
+ is not listed returns an empty array.
+
+ 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(
+ "/search/providers",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"slug": slug}, provider_list_params.ProviderListParams),
+ ),
+ cast_to=ProviderListResponse,
+ )
+
+
+class AsyncProvidersResource(AsyncAPIResource):
+ """Search the web and retrieve content for selected results."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncProvidersResourceWithRawResponse:
+ """
+ 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 AsyncProvidersResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncProvidersResourceWithStreamingResponse:
+ """
+ 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 AsyncProvidersResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ slug: Literal["brave", "exa", "perplexity", "context", "parallel", "valyu", "octen", "you", "tavily", "serpapi"]
+ | 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,
+ ) -> ProviderListResponse:
+ """Lists providers, capabilities, and machine-readable native-option schemas.
+
+ Auto
+ and fallback are strategies, not provider entries. The list is not paginated and
+ contains no latency benchmarks. X-Request-Id identifies the request.
+
+ Args:
+ slug: Optional concrete provider slug filter. Omit to list every provider. A slug that
+ is not listed returns an empty array.
+
+ 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._get(
+ "/search/providers",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"slug": slug}, provider_list_params.ProviderListParams),
+ ),
+ cast_to=ProviderListResponse,
+ )
+
+
+class ProvidersResourceWithRawResponse:
+ def __init__(self, providers: ProvidersResource) -> None:
+ self._providers = providers
+
+ self.list = to_raw_response_wrapper(
+ providers.list,
+ )
+
+
+class AsyncProvidersResourceWithRawResponse:
+ def __init__(self, providers: AsyncProvidersResource) -> None:
+ self._providers = providers
+
+ self.list = async_to_raw_response_wrapper(
+ providers.list,
+ )
+
+
+class ProvidersResourceWithStreamingResponse:
+ def __init__(self, providers: ProvidersResource) -> None:
+ self._providers = providers
+
+ self.list = to_streamed_response_wrapper(
+ providers.list,
+ )
+
+
+class AsyncProvidersResourceWithStreamingResponse:
+ def __init__(self, providers: AsyncProvidersResource) -> None:
+ self._providers = providers
+
+ self.list = async_to_streamed_response_wrapper(
+ providers.list,
+ )
diff --git a/src/kernel/resources/search/search.py b/src/kernel/resources/search/search.py
new file mode 100644
index 00000000..850363c6
--- /dev/null
+++ b/src/kernel/resources/search/search.py
@@ -0,0 +1,536 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import date
+from typing_extensions import Literal
+
+import httpx
+
+from ...types import search_create_params
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from .contents import (
+ ContentsResource,
+ AsyncContentsResource,
+ ContentsResourceWithRawResponse,
+ AsyncContentsResourceWithRawResponse,
+ ContentsResourceWithStreamingResponse,
+ AsyncContentsResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from .providers import (
+ ProvidersResource,
+ AsyncProvidersResource,
+ ProvidersResourceWithRawResponse,
+ AsyncProvidersResourceWithRawResponse,
+ ProvidersResourceWithStreamingResponse,
+ AsyncProvidersResourceWithStreamingResponse,
+)
+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.search.search import Search
+from ...types.strategy_param import StrategyParam
+
+__all__ = ["SearchResource", "AsyncSearchResource"]
+
+
+class SearchResource(SyncAPIResource):
+ """Search the web and retrieve content for selected results."""
+
+ @cached_property
+ def contents(self) -> ContentsResource:
+ """Search the web and retrieve content for selected results."""
+ return ContentsResource(self._client)
+
+ @cached_property
+ def providers(self) -> ProvidersResource:
+ """Search the web and retrieve content for selected results."""
+ return ProvidersResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> SearchResourceWithRawResponse:
+ """
+ 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 SearchResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SearchResourceWithStreamingResponse:
+ """
+ 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 SearchResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ query: str,
+ content: search_create_params.Content | Omit = omit,
+ country: str | Omit = omit,
+ end_date: Union[str, date] | Omit = omit,
+ exclude_domains: SequenceNotStr[str] | Omit = omit,
+ include_domains: SequenceNotStr[str] | Omit = omit,
+ include_raw: bool | Omit = omit,
+ language: str | Omit = omit,
+ max_results: int | Omit = omit,
+ recency: Literal["hour", "day", "week", "month", "year"] | Omit = omit,
+ safe_search: Literal["off", "moderate", "strict"] | Omit = omit,
+ start_date: Union[str, date] | Omit = omit,
+ strategy: StrategyParam | Omit = omit,
+ strict_params: bool | Omit = omit,
+ timeout_ms: 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,
+ ) -> Search:
+ """Returns ranked results from one serving provider.
+
+ The default strategy selects a
+ provider that supports the requested options. The fallback strategy tries
+ providers in the supplied order. Results are not blended across providers.
+ Portable filters may be approximated or omitted according to provider
+ capabilities; warnings describe those outcomes unless strict_params is true.
+ Native options apply only to their selected provider.
+
+ Args:
+ query: Primary search query. A provider-native multi-query option applies only to that
+ provider; other providers in a fallback chain receive this query.
+
+ content: Optional portable content retrieval. Pass true for defaults or an options
+ object. Omission never starts Kernel browser work; provider-supplied content is
+ still returned when available, including when requested through native options.
+ Both inline and deferred retrieval use the same options schema.
+
+ country: ISO 3166-1 alpha-2 search locale preference.
+
+ end_date: Inclusive publication-date upper bound; must not precede start_date. If recency
+ is also supplied, recency takes precedence with a warning. Unsupported or
+ approximated filtering is reported, or rejected under strict_params.
+
+ exclude_domains: Hostname exclusions, with the same best-effort/strict behavior as
+ include_domains. Provider-specific combinations that cannot be represented are
+ reported via warnings or rejected in strict mode.
+
+ include_domains: Hostname inclusion preference, matching a hostname and its subdomains. Empty
+ means unrestricted. Translated, emulated, or dropped with a warning according to
+ provider capability unless strict_params is true. Native boost modes remain
+ advisory and are identified in warnings.
+
+ include_raw: Include untouched per-result payloads and the full serving-provider response in
+ raw fields. Off by default; native top-level outputs such as answer remain
+ available without it.
+
+ language: BCP 47 search language preference.
+
+ max_results: Requested result count from 1 through 100. The effective count is clamped to the
+ serving provider's cap with a warning. Effective native counts are the lower of
+ this limit and supplied provider-native count aliases. Strict mode rejects
+ unsupported counts.
+
+ recency: Relative search window. Takes precedence over start_date/end_date with a warning
+ if both are set. Provider-native recency behavior is retained, including
+ documented hour-to-day widening. Unsupported filters are rejected only in strict
+ mode.
+
+ safe_search: Optional safety preference. Omit to use provider defaults. Unsupported values
+ are dropped with a warning unless strict_params is true. A search filter is not
+ an authorization boundary.
+
+ start_date: Inclusive publication-date lower bound. If recency is also supplied, recency
+ takes precedence with a warning. Provider date semantics, precision, and
+ unsupported filters are reported; unknown source dates are not fabricated or
+ universally post-filtered.
+
+ strategy: Omitted strategy defaults to auto.
+
+ strict_params: When false, unsupported portable parameters are omitted and approximations are
+ described in warnings. When true, every supplied portable parameter must be
+ honored exactly. Requests that cannot be served with those parameters are
+ rejected. This does not guarantee identical rankings or document timestamps
+ across indexes. Authentication and project isolation are always enforced.
+
+ timeout_ms: Overall deadline across search attempts and inline retrieval. No new attempt
+ starts after the deadline. Completed search results survive inline retrieval
+ timeouts.
+
+ 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(
+ "/search",
+ body=maybe_transform(
+ {
+ "query": query,
+ "content": content,
+ "country": country,
+ "end_date": end_date,
+ "exclude_domains": exclude_domains,
+ "include_domains": include_domains,
+ "include_raw": include_raw,
+ "language": language,
+ "max_results": max_results,
+ "recency": recency,
+ "safe_search": safe_search,
+ "start_date": start_date,
+ "strategy": strategy,
+ "strict_params": strict_params,
+ "timeout_ms": timeout_ms,
+ },
+ search_create_params.SearchCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Search,
+ )
+
+ 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,
+ ) -> Search:
+ """
+ Returns the retained search resource exactly as it was returned by POST /search:
+ results, attempts, warnings, usage, and expires_at. No provider is called and
+ nothing is billed. Use it to look up a search by ID for debugging, cost review,
+ or to recover result IDs before calling the contents endpoint. Inline content
+ fetched at search time is included; content fetched later through the contents
+ endpoint is not merged in. Missing, expired, or inaccessible searches
+ return 404.
+
+ 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("/search/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Search,
+ )
+
+
+class AsyncSearchResource(AsyncAPIResource):
+ """Search the web and retrieve content for selected results."""
+
+ @cached_property
+ def contents(self) -> AsyncContentsResource:
+ """Search the web and retrieve content for selected results."""
+ return AsyncContentsResource(self._client)
+
+ @cached_property
+ def providers(self) -> AsyncProvidersResource:
+ """Search the web and retrieve content for selected results."""
+ return AsyncProvidersResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncSearchResourceWithRawResponse:
+ """
+ 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 AsyncSearchResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSearchResourceWithStreamingResponse:
+ """
+ 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 AsyncSearchResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ query: str,
+ content: search_create_params.Content | Omit = omit,
+ country: str | Omit = omit,
+ end_date: Union[str, date] | Omit = omit,
+ exclude_domains: SequenceNotStr[str] | Omit = omit,
+ include_domains: SequenceNotStr[str] | Omit = omit,
+ include_raw: bool | Omit = omit,
+ language: str | Omit = omit,
+ max_results: int | Omit = omit,
+ recency: Literal["hour", "day", "week", "month", "year"] | Omit = omit,
+ safe_search: Literal["off", "moderate", "strict"] | Omit = omit,
+ start_date: Union[str, date] | Omit = omit,
+ strategy: StrategyParam | Omit = omit,
+ strict_params: bool | Omit = omit,
+ timeout_ms: 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,
+ ) -> Search:
+ """Returns ranked results from one serving provider.
+
+ The default strategy selects a
+ provider that supports the requested options. The fallback strategy tries
+ providers in the supplied order. Results are not blended across providers.
+ Portable filters may be approximated or omitted according to provider
+ capabilities; warnings describe those outcomes unless strict_params is true.
+ Native options apply only to their selected provider.
+
+ Args:
+ query: Primary search query. A provider-native multi-query option applies only to that
+ provider; other providers in a fallback chain receive this query.
+
+ content: Optional portable content retrieval. Pass true for defaults or an options
+ object. Omission never starts Kernel browser work; provider-supplied content is
+ still returned when available, including when requested through native options.
+ Both inline and deferred retrieval use the same options schema.
+
+ country: ISO 3166-1 alpha-2 search locale preference.
+
+ end_date: Inclusive publication-date upper bound; must not precede start_date. If recency
+ is also supplied, recency takes precedence with a warning. Unsupported or
+ approximated filtering is reported, or rejected under strict_params.
+
+ exclude_domains: Hostname exclusions, with the same best-effort/strict behavior as
+ include_domains. Provider-specific combinations that cannot be represented are
+ reported via warnings or rejected in strict mode.
+
+ include_domains: Hostname inclusion preference, matching a hostname and its subdomains. Empty
+ means unrestricted. Translated, emulated, or dropped with a warning according to
+ provider capability unless strict_params is true. Native boost modes remain
+ advisory and are identified in warnings.
+
+ include_raw: Include untouched per-result payloads and the full serving-provider response in
+ raw fields. Off by default; native top-level outputs such as answer remain
+ available without it.
+
+ language: BCP 47 search language preference.
+
+ max_results: Requested result count from 1 through 100. The effective count is clamped to the
+ serving provider's cap with a warning. Effective native counts are the lower of
+ this limit and supplied provider-native count aliases. Strict mode rejects
+ unsupported counts.
+
+ recency: Relative search window. Takes precedence over start_date/end_date with a warning
+ if both are set. Provider-native recency behavior is retained, including
+ documented hour-to-day widening. Unsupported filters are rejected only in strict
+ mode.
+
+ safe_search: Optional safety preference. Omit to use provider defaults. Unsupported values
+ are dropped with a warning unless strict_params is true. A search filter is not
+ an authorization boundary.
+
+ start_date: Inclusive publication-date lower bound. If recency is also supplied, recency
+ takes precedence with a warning. Provider date semantics, precision, and
+ unsupported filters are reported; unknown source dates are not fabricated or
+ universally post-filtered.
+
+ strategy: Omitted strategy defaults to auto.
+
+ strict_params: When false, unsupported portable parameters are omitted and approximations are
+ described in warnings. When true, every supplied portable parameter must be
+ honored exactly. Requests that cannot be served with those parameters are
+ rejected. This does not guarantee identical rankings or document timestamps
+ across indexes. Authentication and project isolation are always enforced.
+
+ timeout_ms: Overall deadline across search attempts and inline retrieval. No new attempt
+ starts after the deadline. Completed search results survive inline retrieval
+ timeouts.
+
+ 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(
+ "/search",
+ body=await async_maybe_transform(
+ {
+ "query": query,
+ "content": content,
+ "country": country,
+ "end_date": end_date,
+ "exclude_domains": exclude_domains,
+ "include_domains": include_domains,
+ "include_raw": include_raw,
+ "language": language,
+ "max_results": max_results,
+ "recency": recency,
+ "safe_search": safe_search,
+ "start_date": start_date,
+ "strategy": strategy,
+ "strict_params": strict_params,
+ "timeout_ms": timeout_ms,
+ },
+ search_create_params.SearchCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Search,
+ )
+
+ 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,
+ ) -> Search:
+ """
+ Returns the retained search resource exactly as it was returned by POST /search:
+ results, attempts, warnings, usage, and expires_at. No provider is called and
+ nothing is billed. Use it to look up a search by ID for debugging, cost review,
+ or to recover result IDs before calling the contents endpoint. Inline content
+ fetched at search time is included; content fetched later through the contents
+ endpoint is not merged in. Missing, expired, or inaccessible searches
+ return 404.
+
+ 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("/search/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Search,
+ )
+
+
+class SearchResourceWithRawResponse:
+ def __init__(self, search: SearchResource) -> None:
+ self._search = search
+
+ self.create = to_raw_response_wrapper(
+ search.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ search.retrieve,
+ )
+
+ @cached_property
+ def contents(self) -> ContentsResourceWithRawResponse:
+ """Search the web and retrieve content for selected results."""
+ return ContentsResourceWithRawResponse(self._search.contents)
+
+ @cached_property
+ def providers(self) -> ProvidersResourceWithRawResponse:
+ """Search the web and retrieve content for selected results."""
+ return ProvidersResourceWithRawResponse(self._search.providers)
+
+
+class AsyncSearchResourceWithRawResponse:
+ def __init__(self, search: AsyncSearchResource) -> None:
+ self._search = search
+
+ self.create = async_to_raw_response_wrapper(
+ search.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ search.retrieve,
+ )
+
+ @cached_property
+ def contents(self) -> AsyncContentsResourceWithRawResponse:
+ """Search the web and retrieve content for selected results."""
+ return AsyncContentsResourceWithRawResponse(self._search.contents)
+
+ @cached_property
+ def providers(self) -> AsyncProvidersResourceWithRawResponse:
+ """Search the web and retrieve content for selected results."""
+ return AsyncProvidersResourceWithRawResponse(self._search.providers)
+
+
+class SearchResourceWithStreamingResponse:
+ def __init__(self, search: SearchResource) -> None:
+ self._search = search
+
+ self.create = to_streamed_response_wrapper(
+ search.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ search.retrieve,
+ )
+
+ @cached_property
+ def contents(self) -> ContentsResourceWithStreamingResponse:
+ """Search the web and retrieve content for selected results."""
+ return ContentsResourceWithStreamingResponse(self._search.contents)
+
+ @cached_property
+ def providers(self) -> ProvidersResourceWithStreamingResponse:
+ """Search the web and retrieve content for selected results."""
+ return ProvidersResourceWithStreamingResponse(self._search.providers)
+
+
+class AsyncSearchResourceWithStreamingResponse:
+ def __init__(self, search: AsyncSearchResource) -> None:
+ self._search = search
+
+ self.create = async_to_streamed_response_wrapper(
+ search.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ search.retrieve,
+ )
+
+ @cached_property
+ def contents(self) -> AsyncContentsResourceWithStreamingResponse:
+ """Search the web and retrieve content for selected results."""
+ return AsyncContentsResourceWithStreamingResponse(self._search.contents)
+
+ @cached_property
+ def providers(self) -> AsyncProvidersResourceWithStreamingResponse:
+ """Search the web and retrieve content for selected results."""
+ return AsyncProvidersResourceWithStreamingResponse(self._search.providers)
diff --git a/src/kernel/resources/telemetry/__init__.py b/src/kernel/resources/telemetry/__init__.py
new file mode 100644
index 00000000..77034151
--- /dev/null
+++ b/src/kernel/resources/telemetry/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .telemetry import (
+ TelemetryResource,
+ AsyncTelemetryResource,
+ TelemetryResourceWithRawResponse,
+ AsyncTelemetryResourceWithRawResponse,
+ TelemetryResourceWithStreamingResponse,
+ AsyncTelemetryResourceWithStreamingResponse,
+)
+from .destinations import (
+ DestinationsResource,
+ AsyncDestinationsResource,
+ DestinationsResourceWithRawResponse,
+ AsyncDestinationsResourceWithRawResponse,
+ DestinationsResourceWithStreamingResponse,
+ AsyncDestinationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "DestinationsResource",
+ "AsyncDestinationsResource",
+ "DestinationsResourceWithRawResponse",
+ "AsyncDestinationsResourceWithRawResponse",
+ "DestinationsResourceWithStreamingResponse",
+ "AsyncDestinationsResourceWithStreamingResponse",
+ "TelemetryResource",
+ "AsyncTelemetryResource",
+ "TelemetryResourceWithRawResponse",
+ "AsyncTelemetryResourceWithRawResponse",
+ "TelemetryResourceWithStreamingResponse",
+ "AsyncTelemetryResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/telemetry/destinations.py b/src/kernel/resources/telemetry/destinations.py
new file mode 100644
index 00000000..4d6592fc
--- /dev/null
+++ b/src/kernel/resources/telemetry/destinations.py
@@ -0,0 +1,698 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+
+import httpx
+
+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.telemetry import destination_list_params, destination_create_params, destination_update_params
+from ...types.telemetry.otlp_destination import OtlpDestination
+
+__all__ = ["DestinationsResource", "AsyncDestinationsResource"]
+
+
+class DestinationsResource(SyncAPIResource):
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> DestinationsResourceWithRawResponse:
+ """
+ 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 DestinationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> DestinationsResourceWithStreamingResponse:
+ """
+ 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 DestinationsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ endpoint: str,
+ name: str,
+ description: str | Omit = omit,
+ headers: Dict[str, str] | 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,
+ ) -> OtlpDestination:
+ """Create an OTLP export destination in the authenticated organization.
+
+ Names must
+ be unique within the organization. Requires an organization-scoped credential or
+ dashboard authentication; project-scoped credentials receive a 403.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Kernel appends
+ the signal path itself, so pass `https://api.honeycomb.io` rather than
+ `https://api.honeycomb.io/v1/logs`. If your provider's docs give you a
+ signal-specific URL, drop the trailing `/v1/logs`, `/v1/traces`, or
+ `/v1/metrics` — an endpoint that already carries one is rejected.
+
+ Must be http or https, must resolve to a public address, and must carry no query
+ string or fragment. Examples: `https://api.honeycomb.io`,
+ `https://otlp-gateway-prod-us-east-0.grafana.net/otlp`,
+ `https://otlp.datadoghq.com` (Datadog's OTLP intake for US1, not its logs
+ intake).
+
+ name: Unique within the organization.
+
+ headers: Headers sent with each export request, typically an ingestion key. Encrypted at
+ rest and returned redacted. Names and values must be valid HTTP header tokens,
+ and the names and values together cannot exceed 8192 bytes. Names are matched
+ case-insensitively and stored canonicalized, so supplying two spellings of one
+ header is rejected.
+
+ 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(
+ "/telemetry/destinations",
+ body=maybe_transform(
+ {
+ "endpoint": endpoint,
+ "name": name,
+ "description": description,
+ "headers": headers,
+ },
+ destination_create_params.DestinationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ def retrieve(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OtlpDestination:
+ """
+ Retrieve a customer-visible OTLP destination in the authenticated organization
+ by its ID or name. Project-scoped credentials can retrieve these destinations
+ for selection by workloads in their project. Non-dashboard reads return header
+ values redacted.
+
+ 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_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("/telemetry/destinations/{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
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ def update(
+ self,
+ id_or_name: str,
+ *,
+ description: str | Omit = omit,
+ endpoint: str | Omit = omit,
+ headers: Dict[str, Optional[str]] | Omit = omit,
+ name: str | 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,
+ ) -> OtlpDestination:
+ """Update an OTLP destination.
+
+ Sessions already exporting to it pick up the new
+ values without restarting, which makes this the way to rotate credentials
+ without interrupting export.
+
+ Names must be unique within the organization. Renaming is refused with a 409
+ while a managed auth connection selects this destination by name, since that
+ connection resolves the name on every login. Every other field, including
+ `headers`, stays editable. Requires an organization-scoped credential or
+ dashboard authentication; project-scoped credentials receive a 403.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Same rules as
+ on create.
+
+ headers: Edits stored headers key by key rather than replacing the map. A string value
+ adds or replaces that header, `null` deletes it, and any key you omit is left as
+ it is. Names are matched case-insensitively, so `authorization` replaces a
+ stored `Authorization` rather than adding a second entry. This is the credential
+ rotation path; sessions already exporting pick up the new values without
+ restarting. Names and values must be valid HTTP header tokens, and the names and
+ values together cannot exceed 8192 bytes.
+
+ 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_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("/telemetry/destinations/{id_or_name}", id_or_name=id_or_name),
+ body=maybe_transform(
+ {
+ "description": description,
+ "endpoint": endpoint,
+ "headers": headers,
+ "name": name,
+ },
+ destination_update_params.DestinationUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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[OtlpDestination]:
+ """List customer-visible OTLP export destinations in the authenticated
+ organization.
+
+ Project-scoped credentials can list these destinations for
+ selection by workloads in their project. Non-dashboard reads return header
+ values redacted.
+
+ Args:
+ limit: Limit the number of destinations to return.
+
+ name: Exact-match filter on destination name using the database collation. In
+ production, matching is case- and accent-insensitive.
+
+ offset: Offset the number of destinations to return.
+
+ query: Case-insensitive substring match against destination name or endpoint. IDs match
+ by exact value.
+
+ 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(
+ "/telemetry/destinations",
+ page=SyncOffsetPagination[OtlpDestination],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ destination_list_params.DestinationListParams,
+ ),
+ ),
+ model=OtlpDestination,
+ )
+
+ def delete(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete an OTLP destination.
+
+ Sessions bound to it are still exporting, so the
+ delete is refused with a 409 while any exist; either wait for those sessions to
+ end or delete them first. It is refused the same way while a managed auth
+ connection still selects it, because that connection re-resolves the destination
+ on every login, and while a managed auth login using it is still in progress.
+ Requires an organization-scoped credential or dashboard authentication;
+ project-scoped credentials receive a 403.
+
+ 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_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("/telemetry/destinations/{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
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncDestinationsResource(AsyncAPIResource):
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncDestinationsResourceWithRawResponse:
+ """
+ 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 AsyncDestinationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncDestinationsResourceWithStreamingResponse:
+ """
+ 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 AsyncDestinationsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ endpoint: str,
+ name: str,
+ description: str | Omit = omit,
+ headers: Dict[str, str] | 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,
+ ) -> OtlpDestination:
+ """Create an OTLP export destination in the authenticated organization.
+
+ Names must
+ be unique within the organization. Requires an organization-scoped credential or
+ dashboard authentication; project-scoped credentials receive a 403.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Kernel appends
+ the signal path itself, so pass `https://api.honeycomb.io` rather than
+ `https://api.honeycomb.io/v1/logs`. If your provider's docs give you a
+ signal-specific URL, drop the trailing `/v1/logs`, `/v1/traces`, or
+ `/v1/metrics` — an endpoint that already carries one is rejected.
+
+ Must be http or https, must resolve to a public address, and must carry no query
+ string or fragment. Examples: `https://api.honeycomb.io`,
+ `https://otlp-gateway-prod-us-east-0.grafana.net/otlp`,
+ `https://otlp.datadoghq.com` (Datadog's OTLP intake for US1, not its logs
+ intake).
+
+ name: Unique within the organization.
+
+ headers: Headers sent with each export request, typically an ingestion key. Encrypted at
+ rest and returned redacted. Names and values must be valid HTTP header tokens,
+ and the names and values together cannot exceed 8192 bytes. Names are matched
+ case-insensitively and stored canonicalized, so supplying two spellings of one
+ header is rejected.
+
+ 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(
+ "/telemetry/destinations",
+ body=await async_maybe_transform(
+ {
+ "endpoint": endpoint,
+ "name": name,
+ "description": description,
+ "headers": headers,
+ },
+ destination_create_params.DestinationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ async def retrieve(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OtlpDestination:
+ """
+ Retrieve a customer-visible OTLP destination in the authenticated organization
+ by its ID or name. Project-scoped credentials can retrieve these destinations
+ for selection by workloads in their project. Non-dashboard reads return header
+ values redacted.
+
+ 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_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("/telemetry/destinations/{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
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ async def update(
+ self,
+ id_or_name: str,
+ *,
+ description: str | Omit = omit,
+ endpoint: str | Omit = omit,
+ headers: Dict[str, Optional[str]] | Omit = omit,
+ name: str | 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,
+ ) -> OtlpDestination:
+ """Update an OTLP destination.
+
+ Sessions already exporting to it pick up the new
+ values without restarting, which makes this the way to rotate credentials
+ without interrupting export.
+
+ Names must be unique within the organization. Renaming is refused with a 409
+ while a managed auth connection selects this destination by name, since that
+ connection resolves the name on every login. Every other field, including
+ `headers`, stays editable. Requires an organization-scoped credential or
+ dashboard authentication; project-scoped credentials receive a 403.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Same rules as
+ on create.
+
+ headers: Edits stored headers key by key rather than replacing the map. A string value
+ adds or replaces that header, `null` deletes it, and any key you omit is left as
+ it is. Names are matched case-insensitively, so `authorization` replaces a
+ stored `Authorization` rather than adding a second entry. This is the credential
+ rotation path; sessions already exporting pick up the new values without
+ restarting. Names and values must be valid HTTP header tokens, and the names and
+ values together cannot exceed 8192 bytes.
+
+ 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_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("/telemetry/destinations/{id_or_name}", id_or_name=id_or_name),
+ body=await async_maybe_transform(
+ {
+ "description": description,
+ "endpoint": endpoint,
+ "headers": headers,
+ "name": name,
+ },
+ destination_update_params.DestinationUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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[OtlpDestination, AsyncOffsetPagination[OtlpDestination]]:
+ """List customer-visible OTLP export destinations in the authenticated
+ organization.
+
+ Project-scoped credentials can list these destinations for
+ selection by workloads in their project. Non-dashboard reads return header
+ values redacted.
+
+ Args:
+ limit: Limit the number of destinations to return.
+
+ name: Exact-match filter on destination name using the database collation. In
+ production, matching is case- and accent-insensitive.
+
+ offset: Offset the number of destinations to return.
+
+ query: Case-insensitive substring match against destination name or endpoint. IDs match
+ by exact value.
+
+ 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(
+ "/telemetry/destinations",
+ page=AsyncOffsetPagination[OtlpDestination],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ destination_list_params.DestinationListParams,
+ ),
+ ),
+ model=OtlpDestination,
+ )
+
+ async def delete(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete an OTLP destination.
+
+ Sessions bound to it are still exporting, so the
+ delete is refused with a 409 while any exist; either wait for those sessions to
+ end or delete them first. It is refused the same way while a managed auth
+ connection still selects it, because that connection re-resolves the destination
+ on every login, and while a managed auth login using it is still in progress.
+ Requires an organization-scoped credential or dashboard authentication;
+ project-scoped credentials receive a 403.
+
+ 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_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("/telemetry/destinations/{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
+ ),
+ cast_to=NoneType,
+ )
+
+
+class DestinationsResourceWithRawResponse:
+ def __init__(self, destinations: DestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = to_raw_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ destinations.update,
+ )
+ self.list = to_raw_response_wrapper(
+ destinations.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ destinations.delete,
+ )
+
+
+class AsyncDestinationsResourceWithRawResponse:
+ def __init__(self, destinations: AsyncDestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = async_to_raw_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ destinations.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ destinations.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ destinations.delete,
+ )
+
+
+class DestinationsResourceWithStreamingResponse:
+ def __init__(self, destinations: DestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = to_streamed_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ destinations.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ destinations.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ destinations.delete,
+ )
+
+
+class AsyncDestinationsResourceWithStreamingResponse:
+ def __init__(self, destinations: AsyncDestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = async_to_streamed_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ destinations.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ destinations.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ destinations.delete,
+ )
diff --git a/src/kernel/resources/telemetry/telemetry.py b/src/kernel/resources/telemetry/telemetry.py
new file mode 100644
index 00000000..afdb7434
--- /dev/null
+++ b/src/kernel/resources/telemetry/telemetry.py
@@ -0,0 +1,120 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from .destinations import (
+ DestinationsResource,
+ AsyncDestinationsResource,
+ DestinationsResourceWithRawResponse,
+ AsyncDestinationsResourceWithRawResponse,
+ DestinationsResourceWithStreamingResponse,
+ AsyncDestinationsResourceWithStreamingResponse,
+)
+
+__all__ = ["TelemetryResource", "AsyncTelemetryResource"]
+
+
+class TelemetryResource(SyncAPIResource):
+ @cached_property
+ def destinations(self) -> DestinationsResource:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return DestinationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> TelemetryResourceWithRawResponse:
+ """
+ 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 TelemetryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> TelemetryResourceWithStreamingResponse:
+ """
+ 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 TelemetryResourceWithStreamingResponse(self)
+
+
+class AsyncTelemetryResource(AsyncAPIResource):
+ @cached_property
+ def destinations(self) -> AsyncDestinationsResource:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncDestinationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncTelemetryResourceWithRawResponse:
+ """
+ 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 AsyncTelemetryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncTelemetryResourceWithStreamingResponse:
+ """
+ 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 AsyncTelemetryResourceWithStreamingResponse(self)
+
+
+class TelemetryResourceWithRawResponse:
+ def __init__(self, telemetry: TelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> DestinationsResourceWithRawResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return DestinationsResourceWithRawResponse(self._telemetry.destinations)
+
+
+class AsyncTelemetryResourceWithRawResponse:
+ def __init__(self, telemetry: AsyncTelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> AsyncDestinationsResourceWithRawResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncDestinationsResourceWithRawResponse(self._telemetry.destinations)
+
+
+class TelemetryResourceWithStreamingResponse:
+ def __init__(self, telemetry: TelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> DestinationsResourceWithStreamingResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return DestinationsResourceWithStreamingResponse(self._telemetry.destinations)
+
+
+class AsyncTelemetryResourceWithStreamingResponse:
+ def __init__(self, telemetry: AsyncTelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> AsyncDestinationsResourceWithStreamingResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncDestinationsResourceWithStreamingResponse(self._telemetry.destinations)
diff --git a/src/kernel/resources/vault_provider_configs.py b/src/kernel/resources/vault_provider_configs.py
new file mode 100644
index 00000000..69f29d3d
--- /dev/null
+++ b/src/kernel/resources/vault_provider_configs.py
@@ -0,0 +1,721 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Any, cast
+from typing_extensions import Literal, overload
+
+import httpx
+
+from ..types import (
+ vault_provider_config_list_params,
+ vault_provider_config_create_params,
+ vault_provider_config_update_params,
+)
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import path_template, required_args, 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.vault_provider_config import VaultProviderConfig
+
+__all__ = ["VaultProviderConfigsResource", "AsyncVaultProviderConfigsResource"]
+
+
+class VaultProviderConfigsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> VaultProviderConfigsResourceWithRawResponse:
+ """
+ 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 VaultProviderConfigsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> VaultProviderConfigsResourceWithStreamingResponse:
+ """
+ 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 VaultProviderConfigsResourceWithStreamingResponse(self)
+
+ @overload
+ def create(
+ self,
+ *,
+ credentials: vault_provider_config_create_params.VaultLinkProviderConfigRequestCredentials,
+ name: str,
+ provider: Literal["link"],
+ # 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,
+ ) -> VaultProviderConfig:
+ """Register a configuration shared across the organization's projects.
+
+ Names are
+ unique within the organization; duplicate names return 409 without replacing
+ credentials. A configuration serves many wallets. Secret credentials are never
+ returned. Requires an organization-scoped credential or dashboard
+ authentication; project-scoped credentials receive 403.
+
+ Args:
+ name: Unique within the organization.
+
+ 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
+ """
+ ...
+
+ @overload
+ def create(
+ self,
+ *,
+ credentials: vault_provider_config_create_params.VaultAgentCardProviderConfigRequestCredentials,
+ name: str,
+ provider: Literal["agentcard"],
+ # 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,
+ ) -> VaultProviderConfig:
+ """Register a configuration shared across the organization's projects.
+
+ Names are
+ unique within the organization; duplicate names return 409 without replacing
+ credentials. A configuration serves many wallets. Secret credentials are never
+ returned. Requires an organization-scoped credential or dashboard
+ authentication; project-scoped credentials receive 403.
+
+ Args:
+ name: Unique within the organization.
+
+ 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
+ """
+ ...
+
+ @required_args(["credentials", "name", "provider"])
+ def create(
+ self,
+ *,
+ credentials: vault_provider_config_create_params.VaultLinkProviderConfigRequestCredentials
+ | vault_provider_config_create_params.VaultAgentCardProviderConfigRequestCredentials,
+ name: str,
+ provider: Literal["link"] | Literal["agentcard"],
+ # 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,
+ ) -> VaultProviderConfig:
+ return cast(
+ VaultProviderConfig,
+ self._post(
+ "/vault-provider-configs",
+ body=maybe_transform(
+ {
+ "credentials": credentials,
+ "name": name,
+ "provider": provider,
+ },
+ vault_provider_config_create_params.VaultProviderConfigCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VaultProviderConfig
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ def retrieve(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VaultProviderConfig:
+ """Look up a configuration by ID or name.
+
+ Returns 404 when it does not exist in the
+ organization.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return cast(
+ VaultProviderConfig,
+ self._get(
+ path_template("/vault-provider-configs/{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
+ ),
+ cast_to=cast(
+ Any, VaultProviderConfig
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ def update(
+ self,
+ id_or_name: str,
+ *,
+ credentials: vault_provider_config_update_params.Credentials | Omit = omit,
+ name: str | 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,
+ ) -> VaultProviderConfig:
+ """Update the supplied fields; omitted fields remain unchanged.
+
+ Names must remain
+ unique within the organization. Requires an organization-scoped credential or
+ dashboard authentication; project-scoped credentials receive 403.
+
+ Args:
+ credentials: Fields to update. Omitted credentials are left unchanged. A rejected update
+ leaves existing credentials unchanged.
+
+ name: Unique within the organization.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return cast(
+ VaultProviderConfig,
+ self._patch(
+ path_template("/vault-provider-configs/{id_or_name}", id_or_name=id_or_name),
+ body=maybe_transform(
+ {
+ "credentials": credentials,
+ "name": name,
+ },
+ vault_provider_config_update_params.VaultProviderConfigUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VaultProviderConfig
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ 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[VaultProviderConfig]:
+ """
+ Secret credentials are never returned.
+
+ 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
+ """
+ return self._get_api_list(
+ "/vault-provider-configs",
+ page=SyncOffsetPagination[VaultProviderConfig],
+ 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,
+ },
+ vault_provider_config_list_params.VaultProviderConfigListParams,
+ ),
+ ),
+ model=cast(Any, VaultProviderConfig), # Union types cannot be passed in as arguments in the type system
+ )
+
+ def delete(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete a configuration in the organization.
+
+ Returns 409 while any non-deleted
+ vault item references the configuration, regardless of connection status. Does
+ not delete the external OAuth client or revoke unrelated grants. Requires an
+ organization-scoped credential or dashboard authentication; project-scoped
+ credentials receive 403.
+
+ 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_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("/vault-provider-configs/{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
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncVaultProviderConfigsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncVaultProviderConfigsResourceWithRawResponse:
+ """
+ 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 AsyncVaultProviderConfigsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncVaultProviderConfigsResourceWithStreamingResponse:
+ """
+ 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 AsyncVaultProviderConfigsResourceWithStreamingResponse(self)
+
+ @overload
+ async def create(
+ self,
+ *,
+ credentials: vault_provider_config_create_params.VaultLinkProviderConfigRequestCredentials,
+ name: str,
+ provider: Literal["link"],
+ # 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,
+ ) -> VaultProviderConfig:
+ """Register a configuration shared across the organization's projects.
+
+ Names are
+ unique within the organization; duplicate names return 409 without replacing
+ credentials. A configuration serves many wallets. Secret credentials are never
+ returned. Requires an organization-scoped credential or dashboard
+ authentication; project-scoped credentials receive 403.
+
+ Args:
+ name: Unique within the organization.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def create(
+ self,
+ *,
+ credentials: vault_provider_config_create_params.VaultAgentCardProviderConfigRequestCredentials,
+ name: str,
+ provider: Literal["agentcard"],
+ # 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,
+ ) -> VaultProviderConfig:
+ """Register a configuration shared across the organization's projects.
+
+ Names are
+ unique within the organization; duplicate names return 409 without replacing
+ credentials. A configuration serves many wallets. Secret credentials are never
+ returned. Requires an organization-scoped credential or dashboard
+ authentication; project-scoped credentials receive 403.
+
+ Args:
+ name: Unique within the organization.
+
+ 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
+ """
+ ...
+
+ @required_args(["credentials", "name", "provider"])
+ async def create(
+ self,
+ *,
+ credentials: vault_provider_config_create_params.VaultLinkProviderConfigRequestCredentials
+ | vault_provider_config_create_params.VaultAgentCardProviderConfigRequestCredentials,
+ name: str,
+ provider: Literal["link"] | Literal["agentcard"],
+ # 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,
+ ) -> VaultProviderConfig:
+ return cast(
+ VaultProviderConfig,
+ await self._post(
+ "/vault-provider-configs",
+ body=await async_maybe_transform(
+ {
+ "credentials": credentials,
+ "name": name,
+ "provider": provider,
+ },
+ vault_provider_config_create_params.VaultProviderConfigCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VaultProviderConfig
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ async def retrieve(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VaultProviderConfig:
+ """Look up a configuration by ID or name.
+
+ Returns 404 when it does not exist in the
+ organization.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return cast(
+ VaultProviderConfig,
+ await self._get(
+ path_template("/vault-provider-configs/{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
+ ),
+ cast_to=cast(
+ Any, VaultProviderConfig
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ async def update(
+ self,
+ id_or_name: str,
+ *,
+ credentials: vault_provider_config_update_params.Credentials | Omit = omit,
+ name: str | 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,
+ ) -> VaultProviderConfig:
+ """Update the supplied fields; omitted fields remain unchanged.
+
+ Names must remain
+ unique within the organization. Requires an organization-scoped credential or
+ dashboard authentication; project-scoped credentials receive 403.
+
+ Args:
+ credentials: Fields to update. Omitted credentials are left unchanged. A rejected update
+ leaves existing credentials unchanged.
+
+ name: Unique within the organization.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return cast(
+ VaultProviderConfig,
+ await self._patch(
+ path_template("/vault-provider-configs/{id_or_name}", id_or_name=id_or_name),
+ body=await async_maybe_transform(
+ {
+ "credentials": credentials,
+ "name": name,
+ },
+ vault_provider_config_update_params.VaultProviderConfigUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VaultProviderConfig
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ 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[VaultProviderConfig, AsyncOffsetPagination[VaultProviderConfig]]:
+ """
+ Secret credentials are never returned.
+
+ 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
+ """
+ return self._get_api_list(
+ "/vault-provider-configs",
+ page=AsyncOffsetPagination[VaultProviderConfig],
+ 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,
+ },
+ vault_provider_config_list_params.VaultProviderConfigListParams,
+ ),
+ ),
+ model=cast(Any, VaultProviderConfig), # Union types cannot be passed in as arguments in the type system
+ )
+
+ async def delete(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete a configuration in the organization.
+
+ Returns 409 while any non-deleted
+ vault item references the configuration, regardless of connection status. Does
+ not delete the external OAuth client or revoke unrelated grants. Requires an
+ organization-scoped credential or dashboard authentication; project-scoped
+ credentials receive 403.
+
+ 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_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("/vault-provider-configs/{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
+ ),
+ cast_to=NoneType,
+ )
+
+
+class VaultProviderConfigsResourceWithRawResponse:
+ def __init__(self, vault_provider_configs: VaultProviderConfigsResource) -> None:
+ self._vault_provider_configs = vault_provider_configs
+
+ self.create = to_raw_response_wrapper(
+ vault_provider_configs.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ vault_provider_configs.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ vault_provider_configs.update,
+ )
+ self.list = to_raw_response_wrapper(
+ vault_provider_configs.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ vault_provider_configs.delete,
+ )
+
+
+class AsyncVaultProviderConfigsResourceWithRawResponse:
+ def __init__(self, vault_provider_configs: AsyncVaultProviderConfigsResource) -> None:
+ self._vault_provider_configs = vault_provider_configs
+
+ self.create = async_to_raw_response_wrapper(
+ vault_provider_configs.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ vault_provider_configs.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ vault_provider_configs.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ vault_provider_configs.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ vault_provider_configs.delete,
+ )
+
+
+class VaultProviderConfigsResourceWithStreamingResponse:
+ def __init__(self, vault_provider_configs: VaultProviderConfigsResource) -> None:
+ self._vault_provider_configs = vault_provider_configs
+
+ self.create = to_streamed_response_wrapper(
+ vault_provider_configs.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ vault_provider_configs.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ vault_provider_configs.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ vault_provider_configs.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ vault_provider_configs.delete,
+ )
+
+
+class AsyncVaultProviderConfigsResourceWithStreamingResponse:
+ def __init__(self, vault_provider_configs: AsyncVaultProviderConfigsResource) -> None:
+ self._vault_provider_configs = vault_provider_configs
+
+ self.create = async_to_streamed_response_wrapper(
+ vault_provider_configs.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ vault_provider_configs.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ vault_provider_configs.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ vault_provider_configs.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ vault_provider_configs.delete,
+ )
diff --git a/src/kernel/resources/vaults/__init__.py b/src/kernel/resources/vaults/__init__.py
new file mode 100644
index 00000000..a019b7ee
--- /dev/null
+++ b/src/kernel/resources/vaults/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .items import (
+ ItemsResource,
+ AsyncItemsResource,
+ ItemsResourceWithRawResponse,
+ AsyncItemsResourceWithRawResponse,
+ ItemsResourceWithStreamingResponse,
+ AsyncItemsResourceWithStreamingResponse,
+)
+from .vaults import (
+ VaultsResource,
+ AsyncVaultsResource,
+ VaultsResourceWithRawResponse,
+ AsyncVaultsResourceWithRawResponse,
+ VaultsResourceWithStreamingResponse,
+ AsyncVaultsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ItemsResource",
+ "AsyncItemsResource",
+ "ItemsResourceWithRawResponse",
+ "AsyncItemsResourceWithRawResponse",
+ "ItemsResourceWithStreamingResponse",
+ "AsyncItemsResourceWithStreamingResponse",
+ "VaultsResource",
+ "AsyncVaultsResource",
+ "VaultsResourceWithRawResponse",
+ "AsyncVaultsResourceWithRawResponse",
+ "VaultsResourceWithStreamingResponse",
+ "AsyncVaultsResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/vaults/items.py b/src/kernel/resources/vaults/items.py
new file mode 100644
index 00000000..9829d498
--- /dev/null
+++ b/src/kernel/resources/vaults/items.py
@@ -0,0 +1,2911 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Any, Dict, List, Union, Iterable, cast
+from datetime import datetime
+from typing_extensions import Literal, overload
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, required_args, 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.vaults import (
+ item_events_params,
+ item_update_params,
+ item_upsert_params,
+ item_retrieve_params,
+ item_perform_operation_params,
+)
+from ...types.vaults.vault_item import VaultItem
+from ...types.vaults.item_list_response import ItemListResponse
+from ...types.vaults.item_events_response import ItemEventsResponse
+from ...types.vaults.vault_fill_field_param import VaultFillFieldParam
+from ...types.vaults.card_vault_item_spec_param import CardVaultItemSpecParam
+from ...types.vaults.vault_webmcp_binding_param import VaultWebmcpBindingParam
+from ...types.vaults.vault_checkout_context_param import VaultCheckoutContextParam
+from ...types.vaults.vault_item_operation_response import VaultItemOperationResponse
+from ...types.vaults.credential_vault_item_spec_input_param import CredentialVaultItemSpecInputParam
+from ...types.vaults.credential_vault_item_spec_update_param import CredentialVaultItemSpecUpdateParam
+from ...types.vaults.one_password_credential_account_spec_param import OnePasswordCredentialAccountSpecParam
+
+__all__ = ["ItemsResource", "AsyncItemsResource"]
+
+
+class ItemsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ItemsResourceWithRawResponse:
+ """
+ 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 ItemsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ItemsResourceWithStreamingResponse:
+ """
+ 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 ItemsResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ expand: List[Literal["payment_methods"]] | Omit = omit,
+ wait: 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,
+ ) -> VaultItem:
+ """
+ The response advertises operations that are valid in the item's current state
+ and live data that can be requested through `expand`. Read each operation's
+ description before using it. Expanded data is fetched from the provider and is
+ not persisted in the vault item. Requesting an unavailable expansion returns 409
+ instead of a partial item. Pending credential items return a collection action.
+ Kernel-hosted active collection links are renewed atomically on expiry for ready
+ or pending items without changing the item version. Invoke collect to open a
+ form for a ready item without clearing values. Sensitive credential values are
+ never returned.
+
+ Args:
+ expand: Live fields advertised by `available_expansions` to include in `expanded`.
+
+ wait: Hold for up to this many seconds while the item is pending authorization,
+ approval, or credential collection. Return the current item when ready or when
+ the wait elapses. This does not wait for edits to an already-ready credential;
+ poll GET without wait and compare version to observe changes after collect.
+ Managed auth credentials are created ready, so wait returns immediately.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItem,
+ self._get(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "expand": expand,
+ "wait": wait,
+ },
+ item_retrieve_params.ItemRetrieveParams,
+ ),
+ ),
+ cast_to=cast(Any, VaultItem), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ @overload
+ def update(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CardVaultItemSpecParam,
+ type: Literal["card"] | 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,
+ ) -> VaultItem:
+ """
+ Credential updates require type credential and the current version, and change
+ only values or description; omitted values are preserved, nonempty strings
+ replace, and null or empty strings clear supported fields. Clearing required
+ text/email/password values returns pending_collection; browser forms still
+ require nonempty required inputs. Card updates may omit type for compatibility
+ with legacy requests. Requested cards accept a replacement specification.
+ Pending issuance requests may update provider-supported fields on their existing
+ request, subject to atomic provider approval checks; omitted optional fields
+ remain unchanged and explicit empty lists clear them. Wallet/provider binding
+ and unsupported fields cannot change after authorization starts. An uncertain
+ update enters recovery_required; delete the card and create a new one rather
+ than retrying the update. Checkout cards may be edited between authorizations. A
+ Link card authorized with browser_id and page_url cannot be changed; delete it
+ and create a new card with the updated details.
+
+ Args:
+ spec: Live Link card request. Test-mode card creation is not supported. The spec can
+ be changed with PATCH until the card is authorized at a checkout. Authorize it
+ with the browser at final checkout: Kernel inspects the checkout through WebMCP
+ and internally chooses a merchant-bound Link Pay Token when supported or a
+ virtual card otherwise. Callers never receive or choose how the credential is
+ delivered.
+
+ 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
+ """
+ ...
+
+ @overload
+ def update(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CredentialVaultItemSpecUpdateParam,
+ type: Literal["credential"],
+ version: int,
+ expected_item_id: str | 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,
+ ) -> VaultItem:
+ """
+ Credential updates require type credential and the current version, and change
+ only values or description; omitted values are preserved, nonempty strings
+ replace, and null or empty strings clear supported fields. Clearing required
+ text/email/password values returns pending_collection; browser forms still
+ require nonempty required inputs. Card updates may omit type for compatibility
+ with legacy requests. Requested cards accept a replacement specification.
+ Pending issuance requests may update provider-supported fields on their existing
+ request, subject to atomic provider approval checks; omitted optional fields
+ remain unchanged and explicit empty lists clear them. Wallet/provider binding
+ and unsupported fields cannot change after authorization starts. An uncertain
+ update enters recovery_required; delete the card and create a new one rather
+ than retrying the update. Checkout cards may be edited between authorizations. A
+ Link card authorized with browser_id and page_url cannot be changed; delete it
+ and create a new card with the updated details.
+
+ Args:
+ version: Expected current item version from the latest read.
+
+ expected_item_id: Optional immutable item ID precondition. Returns 409 if the key now identifies a
+ different item. Accepted writes target this immutable ID, preventing
+ replacement-key races. Supply this when submitting a form bound to a previously
+ read item.
+
+ 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
+ """
+ ...
+
+ @required_args(["id_or_name", "spec"], ["id_or_name", "spec", "type", "version"])
+ def update(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CardVaultItemSpecParam | CredentialVaultItemSpecUpdateParam,
+ type: Literal["card"] | Literal["credential"] | Omit = omit,
+ version: int | Omit = omit,
+ expected_item_id: str | 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,
+ ) -> VaultItem:
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItem,
+ self._patch(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ body=maybe_transform(
+ {
+ "spec": spec,
+ "type": type,
+ "version": version,
+ "expected_item_id": expected_item_id,
+ },
+ item_update_params.ItemUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(Any, VaultItem), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ def list(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ItemListResponse:
+ """Credential entries include safe field metadata and non-sensitive values.
+
+ Listing
+ never creates or renews collection sessions; only an existing unexpired active
+ session is included. Use single-item GET or collect to obtain a fresh link.
+
+ 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_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("/vaults/{id_or_name}/items", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ItemListResponse,
+ )
+
+ def delete(
+ self,
+ key: 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Unresolved payment operations normally block deletion, including operations on
+ child cards of a wallet. Deleting a connected Kernel wallet first blocks new
+ payments on it, then removes its enrolled card. If that fails, the wallet is
+ kept and keeps refusing payments; retry the deletion. A Link or Kernel card in
+ recovery_required for issuance, or an AgentCard card in recovery_required whose
+ checkout create response returned no authorization ID, may be explicitly
+ abandoned by deleting that card directly; deleting its wallet or vault remains
+ blocked until the card is deleted. Deleting or recreating an item is not proof
+ that a payment did not occur. Deleting a managed auth credential item leaves the
+ connection and its saved credential unchanged.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def events(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ after: str | Omit = omit,
+ wait: 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,
+ ) -> ItemEventsResponse:
+ """
+ List immutable audit events for a vault item
+
+ Args:
+ after: Return events after this event ID.
+
+ wait: Long-poll for new events for up to this many seconds.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return self._get(
+ path_template("/vaults/{id_or_name}/items/{key}/events", id_or_name=id_or_name, key=key),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "after": after,
+ "wait": wait,
+ },
+ item_events_params.ItemEventsParams,
+ ),
+ ),
+ cast_to=ItemEventsResponse,
+ )
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["authorize"],
+ browser_id: str | Omit = omit,
+ page_url: str | 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name. The browser must have this
+ vault attached.
+
+ page_url: Exact current top-level URL of the final checkout page open in browser_id.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["restart_verification"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ amount: int,
+ currency: str,
+ occurred_at: Union[str, datetime],
+ status: Literal["APPROVED", "DECLINED", "PENDING", "ERROR", "CANCELLED"],
+ transaction_type: Literal[
+ "PURCHASE", "AUTHORIZATION", "CAPTURE", "REFUND", "REVERSAL", "VERIFICATION", "CHARGEBACK", "FRAUD"
+ ],
+ type: Literal["confirm_transaction"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ amount: Actual transaction amount in minor units, no greater than the approved limit.
+
+ currency: Must match the approved purchase currency.
+
+ occurred_at: Time the merchant outcome was observed.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["collect"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ checkout: VaultCheckoutContextParam,
+ type: Literal["prepare_checkout"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ checkout: Required when preparing an unused AgentCard card for a supported checkout
+ processor. Consent is bound to this browser and declared merchant origin, not a
+ tab. Wait for the item's ready_to_submit status before native Pay and submit
+ within its readiness deadline. Unused preparations expire automatically; every
+ preparation is single-use, including after failure or expiry.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ browser_id: str,
+ type: Literal["fill"],
+ fields: Iterable[VaultFillFieldParam] | Omit = omit,
+ page_url: str | Omit = omit,
+ timeout_ms: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name.
+
+ fields: Field bindings, when the item's advertised fill operation requires them. Each
+ selector is searched in the main frame and all descendant frames and must
+ resolve to exactly one editable input or select, matched directly or through a
+ containing element; zero or multiple candidates fail, and no two bindings may
+ resolve to the same element. Select elements match an option by value, not
+ label. All bindings are validated before any write, then filled in request
+ order, stopping at the first failure. If the page navigates or a target
+ disappears, fill stops rather than choosing another page or element.
+
+ page_url: Exact current top-level page URL, including path, query, and fragment. Must
+ match exactly one open page in the browser; zero or multiple matches fail. No
+ prefix or glob matching. When an item allows omitting it, the browser must have
+ exactly one open page.
+
+ timeout_ms: Total operation deadline in milliseconds, not a per-field timeout.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["1pw_create_access_request"],
+ goal: str | Omit = omit,
+ keywords: SequenceNotStr[str] | Omit = omit,
+ reason: str | 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["1pw_access_request_status"],
+ timeout_seconds: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ browser_id: str,
+ page_url: str,
+ type: Literal["1pw_fill"],
+ entry_id: str | Omit = omit,
+ timeout_ms: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name.
+
+ page_url: Exact current top-level page URL. Must match exactly one open page in the
+ browser.
+
+ entry_id: ID of an approved request entry. Required when several approved entries have the
+ page's origin.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["1pw_recover"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ access_token: str,
+ type: Literal["1pw_update_access_token"],
+ access_token_expires_at: Union[str, datetime] | 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ access_token_expires_at: Optional supplied expiry. Omit to clear the old expiry.
+
+ 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
+ """
+ ...
+
+ @overload
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ bindings: Iterable[VaultWebmcpBindingParam],
+ browser_id: str,
+ input: Dict[str, object],
+ page_url: str,
+ tool_ref: str,
+ type: Literal["webmcp_invoke"],
+ timeout_sec: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name.
+
+ input: Public tool arguments with an existing null slot at each binding path. At most
+ 64 KiB after JSON serialization, including substituted values. Never include
+ vault values here.
+
+ page_url: Exact top-level URL from the discovered tool source (fragment omitted). This
+ pins the target page; it does not authorize a destination.
+
+ tool_ref: Opaque reference to the exact live WebMCP registration.
+
+ timeout_sec: Tool invocation timeout in seconds; preflight and response handling have an
+ additional bounded allowance. An indeterminate outcome is not retried.
+
+ 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
+ """
+ ...
+
+ @required_args(
+ ["id_or_name", "type"],
+ ["id_or_name", "amount", "currency", "occurred_at", "status", "transaction_type", "type"],
+ ["id_or_name", "checkout", "type"],
+ ["id_or_name", "browser_id", "type"],
+ ["id_or_name", "browser_id", "page_url", "type"],
+ ["id_or_name", "access_token", "type"],
+ ["id_or_name", "bindings", "browser_id", "input", "page_url", "tool_ref", "type"],
+ )
+ def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["authorize"]
+ | Literal["restart_verification"]
+ | Literal["confirm_transaction"]
+ | Literal["collect"]
+ | Literal["prepare_checkout"]
+ | Literal["fill"]
+ | Literal["1pw_create_access_request"]
+ | Literal["1pw_access_request_status"]
+ | Literal["1pw_fill"]
+ | Literal["1pw_recover"]
+ | Literal["1pw_update_access_token"]
+ | Literal["webmcp_invoke"],
+ browser_id: str | Omit = omit,
+ page_url: str | Omit = omit,
+ amount: int | Omit = omit,
+ currency: str | Omit = omit,
+ occurred_at: Union[str, datetime] | Omit = omit,
+ status: Literal["APPROVED", "DECLINED", "PENDING", "ERROR", "CANCELLED"] | Omit = omit,
+ transaction_type: Literal[
+ "PURCHASE", "AUTHORIZATION", "CAPTURE", "REFUND", "REVERSAL", "VERIFICATION", "CHARGEBACK", "FRAUD"
+ ]
+ | Omit = omit,
+ checkout: VaultCheckoutContextParam | Omit = omit,
+ fields: Iterable[VaultFillFieldParam] | Omit = omit,
+ timeout_ms: int | Omit = omit,
+ goal: str | Omit = omit,
+ keywords: SequenceNotStr[str] | Omit = omit,
+ reason: str | Omit = omit,
+ timeout_seconds: int | Omit = omit,
+ entry_id: str | Omit = omit,
+ access_token: str | Omit = omit,
+ access_token_expires_at: Union[str, datetime] | Omit = omit,
+ bindings: Iterable[VaultWebmcpBindingParam] | Omit = omit,
+ input: Dict[str, object] | Omit = omit,
+ tool_ref: str | Omit = omit,
+ timeout_sec: 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,
+ ) -> VaultItemOperationResponse:
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItemOperationResponse,
+ self._client.with_options(max_retries=0).post(
+ path_template("/vaults/{id_or_name}/items/{key}/operations", id_or_name=id_or_name, key=key),
+ body=maybe_transform(
+ {
+ "type": type,
+ "browser_id": browser_id,
+ "page_url": page_url,
+ "amount": amount,
+ "currency": currency,
+ "occurred_at": occurred_at,
+ "status": status,
+ "transaction_type": transaction_type,
+ "checkout": checkout,
+ "fields": fields,
+ "timeout_ms": timeout_ms,
+ "goal": goal,
+ "keywords": keywords,
+ "reason": reason,
+ "timeout_seconds": timeout_seconds,
+ "entry_id": entry_id,
+ "access_token": access_token,
+ "access_token_expires_at": access_token_expires_at,
+ "bindings": bindings,
+ "input": input,
+ "tool_ref": tool_ref,
+ "timeout_sec": timeout_sec,
+ },
+ item_perform_operation_params.ItemPerformOperationParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VaultItemOperationResponse
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ @overload
+ def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: item_upsert_params.WalletVaultItemRequestSpec,
+ type: Literal["wallet"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ Args:
+ spec: 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. 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
+
+ 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
+ """
+ ...
+
+ @overload
+ def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CardVaultItemSpecParam,
+ type: Literal["card"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ Args:
+ spec: Live Link card request. Test-mode card creation is not supported. The spec can
+ be changed with PATCH until the card is authorized at a checkout. Authorize it
+ with the browser at final checkout: Kernel inspects the checkout through WebMCP
+ and internally chooses a merchant-bound Link Pay Token when supported or a
+ virtual card otherwise. Callers never receive or choose how the credential is
+ delivered.
+
+ 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
+ """
+ ...
+
+ @overload
+ def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: OnePasswordCredentialAccountSpecParam,
+ type: Literal["credential_account"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ 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
+ """
+ ...
+
+ @overload
+ def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CredentialVaultItemSpecInputParam,
+ type: Literal["credential"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ Args:
+ spec: Credential fields are for login and other non-payment credentials. Do not store,
+ collect, or fill credit card data in credential items. Use wallet and card item
+ types for credit cards and payment checkout instead. Field order is preserved in
+ the user-facing collection form, so list fields in the same top-to-bottom order
+ as the website.
+
+ 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
+ """
+ ...
+
+ @required_args(["id_or_name", "spec", "type"])
+ def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: item_upsert_params.WalletVaultItemRequestSpec
+ | CardVaultItemSpecParam
+ | OnePasswordCredentialAccountSpecParam
+ | CredentialVaultItemSpecInputParam,
+ type: Literal["wallet"] | Literal["card"] | Literal["credential_account"] | Literal["credential"],
+ # 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,
+ ) -> VaultItem:
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItem,
+ self._put(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ body=maybe_transform(
+ {
+ "spec": spec,
+ "type": type,
+ },
+ item_upsert_params.ItemUpsertParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(Any, VaultItem), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+
+class AsyncItemsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncItemsResourceWithRawResponse:
+ """
+ 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 AsyncItemsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncItemsResourceWithStreamingResponse:
+ """
+ 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 AsyncItemsResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ expand: List[Literal["payment_methods"]] | Omit = omit,
+ wait: 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,
+ ) -> VaultItem:
+ """
+ The response advertises operations that are valid in the item's current state
+ and live data that can be requested through `expand`. Read each operation's
+ description before using it. Expanded data is fetched from the provider and is
+ not persisted in the vault item. Requesting an unavailable expansion returns 409
+ instead of a partial item. Pending credential items return a collection action.
+ Kernel-hosted active collection links are renewed atomically on expiry for ready
+ or pending items without changing the item version. Invoke collect to open a
+ form for a ready item without clearing values. Sensitive credential values are
+ never returned.
+
+ Args:
+ expand: Live fields advertised by `available_expansions` to include in `expanded`.
+
+ wait: Hold for up to this many seconds while the item is pending authorization,
+ approval, or credential collection. Return the current item when ready or when
+ the wait elapses. This does not wait for edits to an already-ready credential;
+ poll GET without wait and compare version to observe changes after collect.
+ Managed auth credentials are created ready, so wait returns immediately.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItem,
+ await self._get(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "expand": expand,
+ "wait": wait,
+ },
+ item_retrieve_params.ItemRetrieveParams,
+ ),
+ ),
+ cast_to=cast(Any, VaultItem), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ @overload
+ async def update(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CardVaultItemSpecParam,
+ type: Literal["card"] | 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,
+ ) -> VaultItem:
+ """
+ Credential updates require type credential and the current version, and change
+ only values or description; omitted values are preserved, nonempty strings
+ replace, and null or empty strings clear supported fields. Clearing required
+ text/email/password values returns pending_collection; browser forms still
+ require nonempty required inputs. Card updates may omit type for compatibility
+ with legacy requests. Requested cards accept a replacement specification.
+ Pending issuance requests may update provider-supported fields on their existing
+ request, subject to atomic provider approval checks; omitted optional fields
+ remain unchanged and explicit empty lists clear them. Wallet/provider binding
+ and unsupported fields cannot change after authorization starts. An uncertain
+ update enters recovery_required; delete the card and create a new one rather
+ than retrying the update. Checkout cards may be edited between authorizations. A
+ Link card authorized with browser_id and page_url cannot be changed; delete it
+ and create a new card with the updated details.
+
+ Args:
+ spec: Live Link card request. Test-mode card creation is not supported. The spec can
+ be changed with PATCH until the card is authorized at a checkout. Authorize it
+ with the browser at final checkout: Kernel inspects the checkout through WebMCP
+ and internally chooses a merchant-bound Link Pay Token when supported or a
+ virtual card otherwise. Callers never receive or choose how the credential is
+ delivered.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def update(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CredentialVaultItemSpecUpdateParam,
+ type: Literal["credential"],
+ version: int,
+ expected_item_id: str | 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,
+ ) -> VaultItem:
+ """
+ Credential updates require type credential and the current version, and change
+ only values or description; omitted values are preserved, nonempty strings
+ replace, and null or empty strings clear supported fields. Clearing required
+ text/email/password values returns pending_collection; browser forms still
+ require nonempty required inputs. Card updates may omit type for compatibility
+ with legacy requests. Requested cards accept a replacement specification.
+ Pending issuance requests may update provider-supported fields on their existing
+ request, subject to atomic provider approval checks; omitted optional fields
+ remain unchanged and explicit empty lists clear them. Wallet/provider binding
+ and unsupported fields cannot change after authorization starts. An uncertain
+ update enters recovery_required; delete the card and create a new one rather
+ than retrying the update. Checkout cards may be edited between authorizations. A
+ Link card authorized with browser_id and page_url cannot be changed; delete it
+ and create a new card with the updated details.
+
+ Args:
+ version: Expected current item version from the latest read.
+
+ expected_item_id: Optional immutable item ID precondition. Returns 409 if the key now identifies a
+ different item. Accepted writes target this immutable ID, preventing
+ replacement-key races. Supply this when submitting a form bound to a previously
+ read item.
+
+ 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
+ """
+ ...
+
+ @required_args(["id_or_name", "spec"], ["id_or_name", "spec", "type", "version"])
+ async def update(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CardVaultItemSpecParam | CredentialVaultItemSpecUpdateParam,
+ type: Literal["card"] | Literal["credential"] | Omit = omit,
+ version: int | Omit = omit,
+ expected_item_id: str | 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,
+ ) -> VaultItem:
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItem,
+ await self._patch(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ body=await async_maybe_transform(
+ {
+ "spec": spec,
+ "type": type,
+ "version": version,
+ "expected_item_id": expected_item_id,
+ },
+ item_update_params.ItemUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(Any, VaultItem), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ async def list(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ItemListResponse:
+ """Credential entries include safe field metadata and non-sensitive values.
+
+ Listing
+ never creates or renews collection sessions; only an existing unexpired active
+ session is included. Use single-item GET or collect to obtain a fresh link.
+
+ 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_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("/vaults/{id_or_name}/items", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ItemListResponse,
+ )
+
+ async def delete(
+ self,
+ key: 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Unresolved payment operations normally block deletion, including operations on
+ child cards of a wallet. Deleting a connected Kernel wallet first blocks new
+ payments on it, then removes its enrolled card. If that fails, the wallet is
+ kept and keeps refusing payments; retry the deletion. A Link or Kernel card in
+ recovery_required for issuance, or an AgentCard card in recovery_required whose
+ checkout create response returned no authorization ID, may be explicitly
+ abandoned by deleting that card directly; deleting its wallet or vault remains
+ blocked until the card is deleted. Deleting or recreating an item is not proof
+ that a payment did not occur. Deleting a managed auth credential item leaves the
+ connection and its saved credential unchanged.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def events(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ after: str | Omit = omit,
+ wait: 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,
+ ) -> ItemEventsResponse:
+ """
+ List immutable audit events for a vault item
+
+ Args:
+ after: Return events after this event ID.
+
+ wait: Long-poll for new events for up to this many seconds.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return await self._get(
+ path_template("/vaults/{id_or_name}/items/{key}/events", id_or_name=id_or_name, key=key),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "after": after,
+ "wait": wait,
+ },
+ item_events_params.ItemEventsParams,
+ ),
+ ),
+ cast_to=ItemEventsResponse,
+ )
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["authorize"],
+ browser_id: str | Omit = omit,
+ page_url: str | 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name. The browser must have this
+ vault attached.
+
+ page_url: Exact current top-level URL of the final checkout page open in browser_id.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["restart_verification"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ amount: int,
+ currency: str,
+ occurred_at: Union[str, datetime],
+ status: Literal["APPROVED", "DECLINED", "PENDING", "ERROR", "CANCELLED"],
+ transaction_type: Literal[
+ "PURCHASE", "AUTHORIZATION", "CAPTURE", "REFUND", "REVERSAL", "VERIFICATION", "CHARGEBACK", "FRAUD"
+ ],
+ type: Literal["confirm_transaction"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ amount: Actual transaction amount in minor units, no greater than the approved limit.
+
+ currency: Must match the approved purchase currency.
+
+ occurred_at: Time the merchant outcome was observed.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["collect"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ checkout: VaultCheckoutContextParam,
+ type: Literal["prepare_checkout"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ checkout: Required when preparing an unused AgentCard card for a supported checkout
+ processor. Consent is bound to this browser and declared merchant origin, not a
+ tab. Wait for the item's ready_to_submit status before native Pay and submit
+ within its readiness deadline. Unused preparations expire automatically; every
+ preparation is single-use, including after failure or expiry.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ browser_id: str,
+ type: Literal["fill"],
+ fields: Iterable[VaultFillFieldParam] | Omit = omit,
+ page_url: str | Omit = omit,
+ timeout_ms: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name.
+
+ fields: Field bindings, when the item's advertised fill operation requires them. Each
+ selector is searched in the main frame and all descendant frames and must
+ resolve to exactly one editable input or select, matched directly or through a
+ containing element; zero or multiple candidates fail, and no two bindings may
+ resolve to the same element. Select elements match an option by value, not
+ label. All bindings are validated before any write, then filled in request
+ order, stopping at the first failure. If the page navigates or a target
+ disappears, fill stops rather than choosing another page or element.
+
+ page_url: Exact current top-level page URL, including path, query, and fragment. Must
+ match exactly one open page in the browser; zero or multiple matches fail. No
+ prefix or glob matching. When an item allows omitting it, the browser must have
+ exactly one open page.
+
+ timeout_ms: Total operation deadline in milliseconds, not a per-field timeout.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["1pw_create_access_request"],
+ goal: str | Omit = omit,
+ keywords: SequenceNotStr[str] | Omit = omit,
+ reason: str | 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["1pw_access_request_status"],
+ timeout_seconds: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ browser_id: str,
+ page_url: str,
+ type: Literal["1pw_fill"],
+ entry_id: str | Omit = omit,
+ timeout_ms: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name.
+
+ page_url: Exact current top-level page URL. Must match exactly one open page in the
+ browser.
+
+ entry_id: ID of an approved request entry. Required when several approved entries have the
+ page's origin.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["1pw_recover"],
+ # 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ access_token: str,
+ type: Literal["1pw_update_access_token"],
+ access_token_expires_at: Union[str, datetime] | 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ access_token_expires_at: Optional supplied expiry. Omit to clear the old expiry.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ bindings: Iterable[VaultWebmcpBindingParam],
+ browser_id: str,
+ input: Dict[str, object],
+ page_url: str,
+ tool_ref: str,
+ type: Literal["webmcp_invoke"],
+ timeout_sec: 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,
+ ) -> VaultItemOperationResponse:
+ """
+ Retrieve the item first and invoke only an operation listed in
+ `available_operations`, following its natural-language description. Availability
+ is rechecked at execution time; unavailable operations return 409. Authorization
+ and preparation may call an external provider and return updated state. Link
+ cards advertise authorize; pass browser_id and page_url from the final checkout
+ page so Kernel can inspect the checkout. Eligible unused AgentCard cards
+ advertise prepare_checkout, which requires checkout context and obtains device
+ approval before native Square Pay. Keep the returned approval page open, poll
+ until ready_to_submit, then submit before preparation.expires_at. Unused
+ preparations expire automatically and cannot be reused. If spend-request
+ creation is rejected with a non-retryable provider error, the card item is
+ deleted and the provider's error code and message are returned. Rate limits
+ return HTTP 429 and retain the card item; stop, back off, and retry the same
+ authorize operation.
+
+ Ready Kernel cards advertise fill. Fill delivers card details for a few minutes;
+ submit checkout promptly after filling. A repeated fill is safe.
+
+ Fill returns a value-free execution result. Validation failures before writing
+ return 400 (invalid request or targets), 403 (access or destination denied), 404
+ (resource not found), or 409 (item or browser not ready). Once writing starts,
+ known partial failures and indeterminate field outcomes return 200 with status
+ `failed` or `unknown`. Fill never submits the page, so it is safe to retry after
+ a failure, an `unknown` outcome, or a transport error.
+
+ Args:
+ browser_id: Browser session ID, not a reusable browser name.
+
+ input: Public tool arguments with an existing null slot at each binding path. At most
+ 64 KiB after JSON serialization, including substituted values. Never include
+ vault values here.
+
+ page_url: Exact top-level URL from the discovered tool source (fragment omitted). This
+ pins the target page; it does not authorize a destination.
+
+ tool_ref: Opaque reference to the exact live WebMCP registration.
+
+ timeout_sec: Tool invocation timeout in seconds; preflight and response handling have an
+ additional bounded allowance. An indeterminate outcome is not retried.
+
+ 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
+ """
+ ...
+
+ @required_args(
+ ["id_or_name", "type"],
+ ["id_or_name", "amount", "currency", "occurred_at", "status", "transaction_type", "type"],
+ ["id_or_name", "checkout", "type"],
+ ["id_or_name", "browser_id", "type"],
+ ["id_or_name", "browser_id", "page_url", "type"],
+ ["id_or_name", "access_token", "type"],
+ ["id_or_name", "bindings", "browser_id", "input", "page_url", "tool_ref", "type"],
+ )
+ async def perform_operation(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ type: Literal["authorize"]
+ | Literal["restart_verification"]
+ | Literal["confirm_transaction"]
+ | Literal["collect"]
+ | Literal["prepare_checkout"]
+ | Literal["fill"]
+ | Literal["1pw_create_access_request"]
+ | Literal["1pw_access_request_status"]
+ | Literal["1pw_fill"]
+ | Literal["1pw_recover"]
+ | Literal["1pw_update_access_token"]
+ | Literal["webmcp_invoke"],
+ browser_id: str | Omit = omit,
+ page_url: str | Omit = omit,
+ amount: int | Omit = omit,
+ currency: str | Omit = omit,
+ occurred_at: Union[str, datetime] | Omit = omit,
+ status: Literal["APPROVED", "DECLINED", "PENDING", "ERROR", "CANCELLED"] | Omit = omit,
+ transaction_type: Literal[
+ "PURCHASE", "AUTHORIZATION", "CAPTURE", "REFUND", "REVERSAL", "VERIFICATION", "CHARGEBACK", "FRAUD"
+ ]
+ | Omit = omit,
+ checkout: VaultCheckoutContextParam | Omit = omit,
+ fields: Iterable[VaultFillFieldParam] | Omit = omit,
+ timeout_ms: int | Omit = omit,
+ goal: str | Omit = omit,
+ keywords: SequenceNotStr[str] | Omit = omit,
+ reason: str | Omit = omit,
+ timeout_seconds: int | Omit = omit,
+ entry_id: str | Omit = omit,
+ access_token: str | Omit = omit,
+ access_token_expires_at: Union[str, datetime] | Omit = omit,
+ bindings: Iterable[VaultWebmcpBindingParam] | Omit = omit,
+ input: Dict[str, object] | Omit = omit,
+ tool_ref: str | Omit = omit,
+ timeout_sec: 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,
+ ) -> VaultItemOperationResponse:
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItemOperationResponse,
+ await self._client.with_options(max_retries=0).post(
+ path_template("/vaults/{id_or_name}/items/{key}/operations", id_or_name=id_or_name, key=key),
+ body=await async_maybe_transform(
+ {
+ "type": type,
+ "browser_id": browser_id,
+ "page_url": page_url,
+ "amount": amount,
+ "currency": currency,
+ "occurred_at": occurred_at,
+ "status": status,
+ "transaction_type": transaction_type,
+ "checkout": checkout,
+ "fields": fields,
+ "timeout_ms": timeout_ms,
+ "goal": goal,
+ "keywords": keywords,
+ "reason": reason,
+ "timeout_seconds": timeout_seconds,
+ "entry_id": entry_id,
+ "access_token": access_token,
+ "access_token_expires_at": access_token_expires_at,
+ "bindings": bindings,
+ "input": input,
+ "tool_ref": tool_ref,
+ "timeout_sec": timeout_sec,
+ },
+ item_perform_operation_params.ItemPerformOperationParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(
+ Any, VaultItemOperationResponse
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ @overload
+ async def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: item_upsert_params.WalletVaultItemRequestSpec,
+ type: Literal["wallet"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ Args:
+ spec: 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. 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
+
+ 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
+ """
+ ...
+
+ @overload
+ async def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CardVaultItemSpecParam,
+ type: Literal["card"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ Args:
+ spec: Live Link card request. Test-mode card creation is not supported. The spec can
+ be changed with PATCH until the card is authorized at a checkout. Authorize it
+ with the browser at final checkout: Kernel inspects the checkout through WebMCP
+ and internally chooses a merchant-bound Link Pay Token when supported or a
+ virtual card otherwise. Callers never receive or choose how the credential is
+ delivered.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: OnePasswordCredentialAccountSpecParam,
+ type: Literal["credential_account"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: CredentialVaultItemSpecInputParam,
+ type: Literal["credential"],
+ # 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,
+ ) -> VaultItem:
+ """
+ Create an item under a key unique within its vault, or retrieve the existing
+ item when its specification matches. An identical card PUT returns the existing
+ card in any lifecycle state without polling the provider, reauthorizing,
+ replacing aliases, or resetting recovery. Conflicting specifications return 409.
+ Provider-specific authorization requirements and retry behavior are described in
+ the item's request schema. Do not use credential items to store, collect, or
+ fill credit card data, including card numbers (PANs), security codes (CVV/CVC),
+ or expiration dates. Use wallet and card item types for credit cards and payment
+ checkout instead.
+
+ Args:
+ spec: Credential fields are for login and other non-payment credentials. Do not store,
+ collect, or fill credit card data in credential items. Use wallet and card item
+ types for credit cards and payment checkout instead. Field order is preserved in
+ the user-facing collection form, so list fields in the same top-to-bottom order
+ as the website.
+
+ 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
+ """
+ ...
+
+ @required_args(["id_or_name", "spec", "type"])
+ async def upsert(
+ self,
+ key: str,
+ *,
+ id_or_name: str,
+ spec: item_upsert_params.WalletVaultItemRequestSpec
+ | CardVaultItemSpecParam
+ | OnePasswordCredentialAccountSpecParam
+ | CredentialVaultItemSpecInputParam,
+ type: Literal["wallet"] | Literal["card"] | Literal["credential_account"] | Literal["credential"],
+ # 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,
+ ) -> VaultItem:
+ if not id_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ return cast(
+ VaultItem,
+ await self._put(
+ path_template("/vaults/{id_or_name}/items/{key}", id_or_name=id_or_name, key=key),
+ body=await async_maybe_transform(
+ {
+ "spec": spec,
+ "type": type,
+ },
+ item_upsert_params.ItemUpsertParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(Any, VaultItem), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+
+class ItemsResourceWithRawResponse:
+ def __init__(self, items: ItemsResource) -> None:
+ self._items = items
+
+ self.retrieve = to_raw_response_wrapper(
+ items.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ items.update,
+ )
+ self.list = to_raw_response_wrapper(
+ items.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ items.delete,
+ )
+ self.events = to_raw_response_wrapper(
+ items.events,
+ )
+ self.perform_operation = to_raw_response_wrapper(
+ items.perform_operation,
+ )
+ self.upsert = to_raw_response_wrapper(
+ items.upsert,
+ )
+
+
+class AsyncItemsResourceWithRawResponse:
+ def __init__(self, items: AsyncItemsResource) -> None:
+ self._items = items
+
+ self.retrieve = async_to_raw_response_wrapper(
+ items.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ items.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ items.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ items.delete,
+ )
+ self.events = async_to_raw_response_wrapper(
+ items.events,
+ )
+ self.perform_operation = async_to_raw_response_wrapper(
+ items.perform_operation,
+ )
+ self.upsert = async_to_raw_response_wrapper(
+ items.upsert,
+ )
+
+
+class ItemsResourceWithStreamingResponse:
+ def __init__(self, items: ItemsResource) -> None:
+ self._items = items
+
+ self.retrieve = to_streamed_response_wrapper(
+ items.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ items.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ items.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ items.delete,
+ )
+ self.events = to_streamed_response_wrapper(
+ items.events,
+ )
+ self.perform_operation = to_streamed_response_wrapper(
+ items.perform_operation,
+ )
+ self.upsert = to_streamed_response_wrapper(
+ items.upsert,
+ )
+
+
+class AsyncItemsResourceWithStreamingResponse:
+ def __init__(self, items: AsyncItemsResource) -> None:
+ self._items = items
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ items.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ items.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ items.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ items.delete,
+ )
+ self.events = async_to_streamed_response_wrapper(
+ items.events,
+ )
+ self.perform_operation = async_to_streamed_response_wrapper(
+ items.perform_operation,
+ )
+ self.upsert = async_to_streamed_response_wrapper(
+ items.upsert,
+ )
diff --git a/src/kernel/resources/vaults/vaults.py b/src/kernel/resources/vaults/vaults.py
new file mode 100644
index 00000000..ee237487
--- /dev/null
+++ b/src/kernel/resources/vaults/vaults.py
@@ -0,0 +1,571 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .items import (
+ ItemsResource,
+ AsyncItemsResource,
+ ItemsResourceWithRawResponse,
+ AsyncItemsResourceWithRawResponse,
+ ItemsResourceWithStreamingResponse,
+ AsyncItemsResourceWithStreamingResponse,
+)
+from ...types import vault_list_params, vault_upsert_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 ...types.vault import Vault
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.vault_encryption_key import VaultEncryptionKey
+
+__all__ = ["VaultsResource", "AsyncVaultsResource"]
+
+
+class VaultsResource(SyncAPIResource):
+ @cached_property
+ def items(self) -> ItemsResource:
+ return ItemsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> VaultsResourceWithRawResponse:
+ """
+ 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 VaultsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> VaultsResourceWithStreamingResponse:
+ """
+ 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 VaultsResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Vault:
+ """
+ Get a vault
+
+ 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_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("/vaults/{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
+ ),
+ cast_to=Vault,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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[Vault]:
+ """
+ List vaults in the current project
+
+ Args:
+ query: Case-insensitive substring match against vault name. IDs match by exact value.
+
+ 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(
+ "/vaults",
+ page=SyncOffsetPagination[Vault],
+ 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,
+ "query": query,
+ },
+ vault_list_params.VaultListParams,
+ ),
+ ),
+ model=Vault,
+ )
+
+ def delete(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Unresolved payment operations block deletion.
+
+ Deleting a connected Kernel wallet
+ first blocks new payments on it, then removes its enrolled card. If that fails,
+ the wallet is kept and keeps refusing payments; retry the deletion. Reconcile
+ the original attempt with the provider or support first; deleting or recreating
+ an item is not proof that a payment did not occur.
+
+ 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_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("/vaults/{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
+ ),
+ cast_to=NoneType,
+ )
+
+ def retrieve_encryption_key(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VaultEncryptionKey:
+ """
+ Returns the public key that custom credential collection web apps use to encrypt
+ values in the browser. Use this only if you run your own credential collection
+ web app and want values encrypted in the browser, sent to your backend still
+ encrypted, and forwarded to Kernel's API still encrypted. In every other case,
+ including server-side code that already holds the plaintext, use value. The page
+ encrypts each value to this key, your backend forwards the ciphertext unchanged
+ as encrypted_value when creating or updating a credential item in this vault,
+ and Kernel decrypts it.
+
+ 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_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("/vaults/{id_or_name}/encryption_key", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VaultEncryptionKey,
+ )
+
+ def upsert(
+ 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,
+ ) -> Vault:
+ """
+ Free organizations can store up to 3 non-deleted vaults across all projects.
+ Paid plans and active trials have no vault cap. Retrieving an existing vault by
+ name succeeds even at the limit.
+
+ Args:
+ name: Immutable name used to create or retrieve the vault.
+
+ 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(
+ "/vaults",
+ body=maybe_transform({"name": name}, vault_upsert_params.VaultUpsertParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Vault,
+ )
+
+
+class AsyncVaultsResource(AsyncAPIResource):
+ @cached_property
+ def items(self) -> AsyncItemsResource:
+ return AsyncItemsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncVaultsResourceWithRawResponse:
+ """
+ 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 AsyncVaultsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncVaultsResourceWithStreamingResponse:
+ """
+ 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 AsyncVaultsResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Vault:
+ """
+ Get a vault
+
+ 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_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("/vaults/{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
+ ),
+ cast_to=Vault,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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[Vault, AsyncOffsetPagination[Vault]]:
+ """
+ List vaults in the current project
+
+ Args:
+ query: Case-insensitive substring match against vault name. IDs match by exact value.
+
+ 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(
+ "/vaults",
+ page=AsyncOffsetPagination[Vault],
+ 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,
+ "query": query,
+ },
+ vault_list_params.VaultListParams,
+ ),
+ ),
+ model=Vault,
+ )
+
+ async def delete(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Unresolved payment operations block deletion.
+
+ Deleting a connected Kernel wallet
+ first blocks new payments on it, then removes its enrolled card. If that fails,
+ the wallet is kept and keeps refusing payments; retry the deletion. Reconcile
+ the original attempt with the provider or support first; deleting or recreating
+ an item is not proof that a payment did not occur.
+
+ 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_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("/vaults/{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
+ ),
+ cast_to=NoneType,
+ )
+
+ async def retrieve_encryption_key(
+ self,
+ 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VaultEncryptionKey:
+ """
+ Returns the public key that custom credential collection web apps use to encrypt
+ values in the browser. Use this only if you run your own credential collection
+ web app and want values encrypted in the browser, sent to your backend still
+ encrypted, and forwarded to Kernel's API still encrypted. In every other case,
+ including server-side code that already holds the plaintext, use value. The page
+ encrypts each value to this key, your backend forwards the ciphertext unchanged
+ as encrypted_value when creating or updating a credential item in this vault,
+ and Kernel decrypts it.
+
+ 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_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("/vaults/{id_or_name}/encryption_key", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VaultEncryptionKey,
+ )
+
+ async def upsert(
+ 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,
+ ) -> Vault:
+ """
+ Free organizations can store up to 3 non-deleted vaults across all projects.
+ Paid plans and active trials have no vault cap. Retrieving an existing vault by
+ name succeeds even at the limit.
+
+ Args:
+ name: Immutable name used to create or retrieve the vault.
+
+ 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(
+ "/vaults",
+ body=await async_maybe_transform({"name": name}, vault_upsert_params.VaultUpsertParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Vault,
+ )
+
+
+class VaultsResourceWithRawResponse:
+ def __init__(self, vaults: VaultsResource) -> None:
+ self._vaults = vaults
+
+ self.retrieve = to_raw_response_wrapper(
+ vaults.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ vaults.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ vaults.delete,
+ )
+ self.retrieve_encryption_key = to_raw_response_wrapper(
+ vaults.retrieve_encryption_key,
+ )
+ self.upsert = to_raw_response_wrapper(
+ vaults.upsert,
+ )
+
+ @cached_property
+ def items(self) -> ItemsResourceWithRawResponse:
+ return ItemsResourceWithRawResponse(self._vaults.items)
+
+
+class AsyncVaultsResourceWithRawResponse:
+ def __init__(self, vaults: AsyncVaultsResource) -> None:
+ self._vaults = vaults
+
+ self.retrieve = async_to_raw_response_wrapper(
+ vaults.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ vaults.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ vaults.delete,
+ )
+ self.retrieve_encryption_key = async_to_raw_response_wrapper(
+ vaults.retrieve_encryption_key,
+ )
+ self.upsert = async_to_raw_response_wrapper(
+ vaults.upsert,
+ )
+
+ @cached_property
+ def items(self) -> AsyncItemsResourceWithRawResponse:
+ return AsyncItemsResourceWithRawResponse(self._vaults.items)
+
+
+class VaultsResourceWithStreamingResponse:
+ def __init__(self, vaults: VaultsResource) -> None:
+ self._vaults = vaults
+
+ self.retrieve = to_streamed_response_wrapper(
+ vaults.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ vaults.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ vaults.delete,
+ )
+ self.retrieve_encryption_key = to_streamed_response_wrapper(
+ vaults.retrieve_encryption_key,
+ )
+ self.upsert = to_streamed_response_wrapper(
+ vaults.upsert,
+ )
+
+ @cached_property
+ def items(self) -> ItemsResourceWithStreamingResponse:
+ return ItemsResourceWithStreamingResponse(self._vaults.items)
+
+
+class AsyncVaultsResourceWithStreamingResponse:
+ def __init__(self, vaults: AsyncVaultsResource) -> None:
+ self._vaults = vaults
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ vaults.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ vaults.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ vaults.delete,
+ )
+ self.retrieve_encryption_key = async_to_streamed_response_wrapper(
+ vaults.retrieve_encryption_key,
+ )
+ self.upsert = async_to_streamed_response_wrapper(
+ vaults.upsert,
+ )
+
+ @cached_property
+ def items(self) -> AsyncItemsResourceWithStreamingResponse:
+ return AsyncItemsResourceWithStreamingResponse(self._vaults.items)
diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py
index 3838047e..a7735052 100644
--- a/src/kernel/types/__init__.py
+++ b/src/kernel/types/__init__.py
@@ -2,6 +2,14 @@
from __future__ import annotations
+from . import browsers
+from .. import _compat
+from .tags import Tags as Tags
+from .proxy import Proxy as Proxy
+from .usage import Usage as Usage
+from .vault import Vault as Vault
+from .result import Result as Result
+from .search import Search as Search
from .shared import (
LogEvent as LogEvent,
AppAction as AppAction,
@@ -13,44 +21,95 @@
BrowserViewport as BrowserViewport,
BrowserExtension as BrowserExtension,
)
+from .target import Target as Target
+from .api_key import APIKey as APIKey
+from .attempt import Attempt as Attempt
+from .browser import Browser as Browser
from .profile import Profile as Profile
from .project import Project as Project
+from .warning import Warning as Warning
+from .analysis import Analysis as Analysis
+from .evidence import Evidence as Evidence
from .credential import Credential as Credential
+from .tags_param import TagsParam as TagsParam
from .browser_pool import BrowserPool as BrowserPool
+from .browser_proxy import BrowserProxy as BrowserProxy
from .browser_usage import BrowserUsage as BrowserUsage
+from .browser_memory import BrowserMemory as BrowserMemory
+from .recommendation import Recommendation as Recommendation
+from .strategy_param import StrategyParam as StrategyParam
from .app_list_params import AppListParams as AppListParams
+from .audit_log_entry import AuditLogEntry as AuditLogEntry
+from .created_api_key import CreatedAPIKey as CreatedAPIKey
+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
+from .proxy_list_params import ProxyListParams as ProxyListParams
+from .vault_list_params import VaultListParams as VaultListParams
+from .browser_proxy_mode import BrowserProxyMode as BrowserProxyMode
from .proxy_check_params import ProxyCheckParams as ProxyCheckParams
+from .api_key_list_params import APIKeyListParams as APIKeyListParams
from .browser_curl_params import BrowserCurlParams as BrowserCurlParams
from .browser_list_params import BrowserListParams as BrowserListParams
-from .browser_persistence import BrowserPersistence as BrowserPersistence
+from .browser_repl_params import BrowserReplParams as BrowserReplParams
+from .browser_repl_result import BrowserReplResult as BrowserReplResult
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_update_params import ProxyUpdateParams as ProxyUpdateParams
+from .vault_upsert_params import VaultUpsertParams as VaultUpsertParams
+from .browser_proxy_config import BrowserProxyConfig as BrowserProxyConfig
+from .browser_repl_content import BrowserReplContent as BrowserReplContent
+from .browser_video_memory import BrowserVideoMemory as BrowserVideoMemory
from .proxy_check_response import ProxyCheckResponse as ProxyCheckResponse
+from .search_create_params import SearchCreateParams as SearchCreateParams
+from .vault_encryption_key import VaultEncryptionKey as VaultEncryptionKey
+from .api_key_create_params import APIKeyCreateParams as APIKeyCreateParams
+from .api_key_rotate_params import APIKeyRotateParams as APIKeyRotateParams
+from .api_key_update_params import APIKeyUpdateParams as APIKeyUpdateParams
+from .audit_log_list_params import AuditLogListParams as AuditLogListParams
from .browser_create_params import BrowserCreateParams as BrowserCreateParams
from .browser_curl_response import BrowserCurlResponse as BrowserCurlResponse
-from .browser_delete_params import BrowserDeleteParams as BrowserDeleteParams
from .browser_list_response import BrowserListResponse as BrowserListResponse
from .browser_update_params import BrowserUpdateParams as BrowserUpdateParams
+from .extension_list_params import ExtensionListParams as ExtensionListParams
from .profile_create_params import ProfileCreateParams as ProfileCreateParams
+from .profile_update_params import ProfileUpdateParams as ProfileUpdateParams
from .project_create_params import ProjectCreateParams as ProjectCreateParams
from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
+from .provider_target_param import ProviderTargetParam as ProviderTargetParam
from .proxy_create_response import ProxyCreateResponse as ProxyCreateResponse
+from .proxy_update_response import ProxyUpdateResponse as ProxyUpdateResponse
+from .recommendation_result import RecommendationResult as RecommendationResult
+from .vault_provider_config import VaultProviderConfig as VaultProviderConfig
+from .vault_reference_param import VaultReferenceParam as VaultReferenceParam
+from .browser_memory_request import BrowserMemoryRequest as BrowserMemoryRequest
+from .browser_network_config import BrowserNetworkConfig as BrowserNetworkConfig
from .credential_list_params import CredentialListParams as CredentialListParams
from .deployment_list_params import DeploymentListParams as DeploymentListParams
from .deployment_state_event import DeploymentStateEvent as DeploymentStateEvent
+from .extension_get_response import ExtensionGetResponse as ExtensionGetResponse
from .invocation_list_params import InvocationListParams as InvocationListParams
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_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
from .extension_upload_params import ExtensionUploadParams as ExtensionUploadParams
+from .profile_download_params import ProfileDownloadParams as ProfileDownloadParams
from .proxy_retrieve_response import ProxyRetrieveResponse as ProxyRetrieveResponse
+from .browser_pool_list_params import BrowserPoolListParams as BrowserPoolListParams
+from .config_registry_response import ConfigRegistryResponse as ConfigRegistryResponse
from .credential_create_params import CredentialCreateParams as CredentialCreateParams
from .credential_provider_item import CredentialProviderItem as CredentialProviderItem
from .credential_update_params import CredentialUpdateParams as CredentialUpdateParams
@@ -61,13 +120,14 @@
from .invocation_follow_params import InvocationFollowParams as InvocationFollowParams
from .invocation_list_response import InvocationListResponse as InvocationListResponse
from .invocation_update_params import InvocationUpdateParams as InvocationUpdateParams
-from .browser_persistence_param import BrowserPersistenceParam as BrowserPersistenceParam
+from .browser_repl_text_content import BrowserReplTextContent as BrowserReplTextContent
from .browser_retrieve_response import BrowserRetrieveResponse as BrowserRetrieveResponse
from .extension_upload_response import ExtensionUploadResponse as ExtensionUploadResponse
from .browser_pool_create_params import BrowserPoolCreateParams as BrowserPoolCreateParams
from .browser_pool_delete_params import BrowserPoolDeleteParams as BrowserPoolDeleteParams
-from .browser_pool_list_response import BrowserPoolListResponse as BrowserPoolListResponse
from .browser_pool_update_params import BrowserPoolUpdateParams as BrowserPoolUpdateParams
+from .browser_proxy_config_param import BrowserProxyConfigParam as BrowserProxyConfigParam
+from .browser_repl_image_content import BrowserReplImageContent as BrowserReplImageContent
from .deployment_create_response import DeploymentCreateResponse as DeploymentCreateResponse
from .deployment_follow_response import DeploymentFollowResponse as DeploymentFollowResponse
from .invocation_create_response import InvocationCreateResponse as InvocationCreateResponse
@@ -75,19 +135,50 @@
from .invocation_update_response import InvocationUpdateResponse as InvocationUpdateResponse
from .browser_pool_acquire_params import BrowserPoolAcquireParams as BrowserPoolAcquireParams
from .browser_pool_release_params import BrowserPoolReleaseParams as BrowserPoolReleaseParams
+from .config_registry_list_params import ConfigRegistryListParams as ConfigRegistryListParams
+from .browser_network_config_param import BrowserNetworkConfigParam as BrowserNetworkConfigParam
+from .browser_network_update_param import BrowserNetworkUpdateParam as BrowserNetworkUpdateParam
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
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
from .credential_provider_create_params import CredentialProviderCreateParams as CredentialProviderCreateParams
-from .credential_provider_list_response import CredentialProviderListResponse as CredentialProviderListResponse
from .credential_provider_update_params import CredentialProviderUpdateParams as CredentialProviderUpdateParams
from .invocation_list_browsers_response import InvocationListBrowsersResponse as InvocationListBrowsersResponse
+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,
)
from .extension_download_from_chrome_store_params import (
ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams,
)
+
+# Rebuild cyclical models only after all modules are imported.
+# This ensures that, when building the deferred (due to cyclical references) model schema,
+# Pydantic can resolve the necessary references.
+# See: https://github.com/pydantic/pydantic/issues/11250 for more context.
+if _compat.PYDANTIC_V1:
+ browsers.browser_call_stack.BrowserCallStack.update_forward_refs() # type: ignore
+ browsers.browser_console_error_event.BrowserConsoleErrorEvent.update_forward_refs() # type: ignore
+ browsers.browser_console_log_event.BrowserConsoleLogEvent.update_forward_refs() # type: ignore
+ browsers.telemetry_events_response.TelemetryEventsResponse.update_forward_refs() # type: ignore
+ browsers.telemetry_stream_response.TelemetryStreamResponse.update_forward_refs() # type: ignore
+else:
+ browsers.browser_call_stack.BrowserCallStack.model_rebuild(_parent_namespace_depth=0)
+ browsers.browser_console_error_event.BrowserConsoleErrorEvent.model_rebuild(_parent_namespace_depth=0)
+ browsers.browser_console_log_event.BrowserConsoleLogEvent.model_rebuild(_parent_namespace_depth=0)
+ browsers.telemetry_events_response.TelemetryEventsResponse.model_rebuild(_parent_namespace_depth=0)
+ browsers.telemetry_stream_response.TelemetryStreamResponse.model_rebuild(_parent_namespace_depth=0)
diff --git a/src/kernel/types/analysis.py b/src/kernel/types/analysis.py
new file mode 100644
index 00000000..6ce7c3b1
--- /dev/null
+++ b/src/kernel/types/analysis.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from .._models import BaseModel
+from .shared.error_model import ErrorModel
+
+__all__ = ["Analysis"]
+
+
+class Analysis(BaseModel):
+ id: str
+ """Discovery run ID used to poll analysis status."""
+
+ created_at: datetime
+ """Time the analysis was created."""
+
+ expires_at: datetime
+ """Deadline after which a still-running analysis becomes expired."""
+
+ failure: Optional[ErrorModel] = None
+ """Present for failed, canceled, or expired analyses.
+
+ Messages contain safe retry guidance rather than internal workflow errors.
+ """
+
+ finished_at: Optional[datetime] = None
+ """Time the analysis reached a terminal status. Null while it is running."""
+
+ status: Literal["running", "completed", "failed", "canceled", "expired"]
+ """Lifecycle status of a background analysis."""
+
+ intent: Optional[str] = None
+ """The workload description supplied for this analysis.
+
+ Null when the analysis only tested connectivity.
+ """
diff --git a/src/kernel/types/analysis_summary.py b/src/kernel/types/analysis_summary.py
new file mode 100644
index 00000000..fab0d8d8
--- /dev/null
+++ b/src/kernel/types/analysis_summary.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .target import Target
+from .._models import BaseModel
+from .analysis import Analysis
+
+__all__ = ["AnalysisSummary"]
+
+
+class AnalysisSummary(BaseModel):
+ analysis: Analysis
+
+ target: Target
diff --git a/src/kernel/types/api_key.py b/src/kernel/types/api_key.py
new file mode 100644
index 00000000..5dd0b02c
--- /dev/null
+++ b/src/kernel/types/api_key.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["APIKey", "CreatedBy", "Policy", "PolicyStatement"]
+
+
+class CreatedBy(BaseModel):
+ id: str
+ """Kernel user ID of the creator."""
+
+ email: str
+ """Email address of the creator."""
+
+ name: Optional[str] = None
+ """Display name of the creator, if available."""
+
+
+class PolicyStatement(BaseModel):
+ action: str
+ """Permitted action, such as browsers:use or vaults:read."""
+
+ resource: str
+ """Resource the action applies to, in the format projects/{project_id}/{kind}/{id}.
+
+ Wildcards are supported.
+ """
+
+
+class Policy(BaseModel):
+ """Permissions granted to a scoped API key.
+
+ Absent for an ordinary key, which keeps unrestricted access within its organization or project.
+ """
+
+ statements: List[PolicyStatement]
+ """Allow statements. Every action and resource not listed is denied."""
+
+
+class APIKey(BaseModel):
+ id: str
+ """Unique API key identifier"""
+
+ created_at: datetime
+ """When the API key was created"""
+
+ created_by: CreatedBy
+
+ deleted_at: Optional[datetime] = None
+ """When the API key was deleted (soft-deleted).
+
+ Null for keys that have not been deleted.
+ """
+
+ expires_at: Optional[datetime] = None
+ """When the API key expires"""
+
+ masked_key: str
+ """Masked version of the API key"""
+
+ name: str
+ """Label for the API key.
+
+ API keys are not addressable by name; use the ID or key identifier for stable
+ references.
+ """
+
+ project_id: Optional[str] = None
+ """Project identifier for project-scoped API keys. Null means org-wide."""
+
+ project_name: Optional[str] = None
+ """Project name for project-scoped API keys.
+
+ Null means the key is org-wide or the project name is unavailable.
+ """
+
+ policy: Optional[Policy] = None
+ """Permissions granted to a scoped API key.
+
+ Absent for an ordinary key, which keeps unrestricted access within its
+ organization or project.
+ """
diff --git a/src/kernel/types/api_key_create_params.py b/src/kernel/types/api_key_create_params.py
new file mode 100644
index 00000000..86d0ccf3
--- /dev/null
+++ b/src/kernel/types/api_key_create_params.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable, Optional
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["APIKeyCreateParams", "Policy", "PolicyStatement"]
+
+
+class APIKeyCreateParams(TypedDict, total=False):
+ name: Required[str]
+ """Label for the API key (1-255 characters). API keys are not addressable by name."""
+
+ days_to_expire: Optional[int]
+ """Number of days until expiry, up to 3650.
+
+ Use null for never. Cannot be combined with expires_at.
+ """
+
+ expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Exact expiry time.
+
+ Must be in the future and no later than the issuing credential's own expiry.
+ Cannot be combined with days_to_expire.
+ """
+
+ policy: Policy
+ """
+ Issue a scoped key that may only perform the listed actions on the listed
+ resources.
+ """
+
+ project_id: Optional[str]
+ """Unique project identifier"""
+
+
+class PolicyStatement(TypedDict, total=False):
+ action: Required[str]
+ """Permitted action, such as browsers:use or vaults:read."""
+
+ resource: Required[str]
+ """Resource the action applies to, in the format projects/{project_id}/{kind}/{id}.
+
+ Wildcards are supported.
+ """
+
+
+class Policy(TypedDict, total=False):
+ """
+ Issue a scoped key that may only perform the listed actions on the listed resources.
+ """
+
+ statements: Required[Iterable[PolicyStatement]]
+ """Allow statements. Every action and resource not listed is denied."""
diff --git a/src/kernel/types/api_key_list_params.py b/src/kernel/types/api_key_list_params.py
new file mode 100644
index 00000000..95b8af5f
--- /dev/null
+++ b/src/kernel/types/api_key_list_params.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["APIKeyListParams"]
+
+
+class APIKeyListParams(TypedDict, total=False):
+ include_deleted: bool
+ """Deprecated: use status=all instead.
+
+ When true, include deleted (soft-deleted) API keys in the results for audit
+ purposes.
+ """
+
+ limit: int
+ """Maximum number of results to return"""
+
+ name: str
+ """Exact-match filter on API key name using the database collation.
+
+ In production, matching is case- and accent-insensitive. Names are not required
+ to be unique, so multiple keys may match. When status=all or
+ include_deleted=true is set, soft-deleted keys with the same name may also
+ match.
+ """
+
+ offset: int
+ """Number of results to skip"""
+
+ query: str
+ """Case-insensitive substring match against API key name, creator, and project.
+
+ API key identifiers and masked keys match by exact value or prefix.
+ """
+
+ sort_by: Literal["created_at", "name", "expires_at"]
+ """Field to sort API keys by."""
+
+ sort_direction: Literal["asc", "desc"]
+ """Sort direction for API keys."""
+
+ status: Literal["active", "deleted", "all"]
+ """Filter API keys by status.
+
+ "active" returns keys that are not deleted (default; expired-but-not-deleted
+ keys are still included), "deleted" returns only soft-deleted keys, "all"
+ returns both.
+ """
diff --git a/src/kernel/types/api_key_retrieve_params.py b/src/kernel/types/api_key_retrieve_params.py
new file mode 100644
index 00000000..e2b9ea3b
--- /dev/null
+++ b/src/kernel/types/api_key_retrieve_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__ = ["APIKeyRetrieveParams"]
+
+
+class APIKeyRetrieveParams(TypedDict, total=False):
+ include_deleted: bool
+ """
+ When true, return the API key even if it has been deleted (soft-deleted), for
+ audit purposes. Defaults to false, which returns 404 for a deleted key.
+ """
diff --git a/src/kernel/types/api_key_rotate_params.py b/src/kernel/types/api_key_rotate_params.py
new file mode 100644
index 00000000..3810e589
--- /dev/null
+++ b/src/kernel/types/api_key_rotate_params.py
@@ -0,0 +1,23 @@
+# 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__ = ["APIKeyRotateParams"]
+
+
+class APIKeyRotateParams(TypedDict, total=False):
+ days_to_expire: Optional[int]
+ """Lifetime in days for the new key, up to 3650.
+
+ Omit to reuse the rotated key's original lifetime, or never-expires if it had
+ none.
+ """
+
+ expire_in_days: Optional[int]
+ """Grace period in days before the rotated key expires.
+
+ Use 0 to expire it immediately. Omit for the default grace period of 7 days.
+ """
diff --git a/src/kernel/types/browser_delete_params.py b/src/kernel/types/api_key_update_params.py
similarity index 52%
rename from src/kernel/types/browser_delete_params.py
rename to src/kernel/types/api_key_update_params.py
index 4c5b1c6a..2e917607 100644
--- a/src/kernel/types/browser_delete_params.py
+++ b/src/kernel/types/api_key_update_params.py
@@ -4,9 +4,9 @@
from typing_extensions import Required, TypedDict
-__all__ = ["BrowserDeleteParams"]
+__all__ = ["APIKeyUpdateParams"]
-class BrowserDeleteParams(TypedDict, total=False):
- persistent_id: Required[str]
- """Persistent browser identifier"""
+class APIKeyUpdateParams(TypedDict, total=False):
+ name: Required[str]
+ """New API key name"""
diff --git a/src/kernel/types/app_list_response.py b/src/kernel/types/app_list_response.py
index 338f506d..91ad648e 100644
--- a/src/kernel/types/app_list_response.py
+++ b/src/kernel/types/app_list_response.py
@@ -25,7 +25,12 @@ class AppListResponse(BaseModel):
"""Deployment ID"""
env_vars: Dict[str, str]
- """Environment variables configured for this app version"""
+ """Environment variables configured for this app version.
+
+ Values are redacted for API key, OAuth, and managed-auth callers, which receive
+ every key with an empty string value. Only dashboard sessions receive the actual
+ values.
+ """
region: Literal["aws.us-east-1a"]
"""Deployment region code"""
diff --git a/src/kernel/types/attempt.py b/src/kernel/types/attempt.py
new file mode 100644
index 00000000..70863016
--- /dev/null
+++ b/src/kernel/types/attempt.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["Attempt"]
+
+
+class Attempt(BaseModel):
+ duration_ms: int
+
+ outcome: Literal["success", "empty", "error", "timeout"]
+
+ provider: str
+
+ error_code: Optional[str] = None
+
+ retryable: Optional[bool] = None
diff --git a/src/kernel/types/audit_log_entry.py b/src/kernel/types/audit_log_entry.py
new file mode 100644
index 00000000..dd998098
--- /dev/null
+++ b/src/kernel/types/audit_log_entry.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["AuditLogEntry"]
+
+
+class AuditLogEntry(BaseModel):
+ auth_strategy: str
+ """Authentication strategy used for the request."""
+
+ client_ip: str
+ """Client IP address."""
+
+ domain: str
+ """Request host."""
+
+ duration_ms: int
+ """Request duration in milliseconds."""
+
+ email: str
+ """Email of the authenticated user at request time, if any."""
+
+ method: str
+ """HTTP method."""
+
+ path: str
+ """Request path."""
+
+ route: str
+ """Matched API route pattern, if available."""
+
+ status: int
+ """HTTP response status code."""
+
+ timestamp: datetime
+ """UTC time when the request was received."""
+
+ user_agent: str
+ """User agent header."""
+
+ user_id: str
+ """ID of the authenticated user, if any."""
diff --git a/src/kernel/types/audit_log_export_chunk_params.py b/src/kernel/types/audit_log_export_chunk_params.py
new file mode 100644
index 00000000..5cc07b03
--- /dev/null
+++ b/src/kernel/types/audit_log_export_chunk_params.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+
+__all__ = ["AuditLogExportChunkParams"]
+
+
+class AuditLogExportChunkParams(TypedDict, total=False):
+ end: Required[Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]]
+ """Upper bound (exclusive) for the audit record timestamp."""
+
+ start: Required[Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]]
+ """Lower bound (inclusive) for the audit record timestamp."""
+
+ auth_strategy: str
+ """Filter by authentication strategy."""
+
+ cursor: str
+ """Opaque cursor from X-Next-Cursor for the next chunk of older records."""
+
+ exclude_method: SequenceNotStr[str]
+ """Filter out results by HTTP method."""
+
+ format: Literal["jsonl", "jsonl.gz"]
+ """Encoding for the returned chunk."""
+
+ limit: int
+ """Maximum number of records to return in this chunk."""
+
+ method: str
+ """Filter by HTTP method."""
+
+ search: str
+ """Free-text search over path, user ID, email, client IP, and status."""
+
+ search_user_id: SequenceNotStr[str]
+ """Additional user IDs to OR into free-text search."""
+
+ service: str
+ """Filter by service name."""
diff --git a/src/kernel/types/audit_log_list_params.py b/src/kernel/types/audit_log_list_params.py
new file mode 100644
index 00000000..b3329e70
--- /dev/null
+++ b/src/kernel/types/audit_log_list_params.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+
+__all__ = ["AuditLogListParams"]
+
+
+class AuditLogListParams(TypedDict, total=False):
+ end: Required[Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]]
+ """Upper bound (exclusive) for the audit record timestamp."""
+
+ start: Required[Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]]
+ """Lower bound (inclusive) for the audit record timestamp."""
+
+ auth_strategy: str
+ """Filter by authentication strategy."""
+
+ exclude_method: SequenceNotStr[str]
+ """Filter out results by HTTP method."""
+
+ limit: int
+ """Maximum number of results to return."""
+
+ method: str
+ """Filter by HTTP method."""
+
+ page_token: str
+ """Opaque page token from X-Next-Page-Token for the next page of older records."""
+
+ search: str
+ """Free-text search over path, user ID, email, client IP, and status."""
+
+ search_user_id: SequenceNotStr[str]
+ """Additional user IDs to OR into free-text search."""
+
+ service: str
+ """Filter by service name."""
diff --git a/src/kernel/types/audit_logs/__init__.py b/src/kernel/types/audit_logs/__init__.py
new file mode 100644
index 00000000..0a6f41ef
--- /dev/null
+++ b/src/kernel/types/audit_logs/__init__.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .audit_log_export_destination import AuditLogExportDestination as AuditLogExportDestination
+from .export_destination_list_params import ExportDestinationListParams as ExportDestinationListParams
+from .export_destination_create_params import ExportDestinationCreateParams as ExportDestinationCreateParams
+from .export_destination_update_params import ExportDestinationUpdateParams as ExportDestinationUpdateParams
+from .audit_log_export_destination_test_result import (
+ AuditLogExportDestinationTestResult as AuditLogExportDestinationTestResult,
+)
diff --git a/src/kernel/types/audit_logs/audit_log_export_destination.py b/src/kernel/types/audit_logs/audit_log_export_destination.py
new file mode 100644
index 00000000..0897cdf7
--- /dev/null
+++ b/src/kernel/types/audit_logs/audit_log_export_destination.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["AuditLogExportDestination"]
+
+
+class AuditLogExportDestination(BaseModel):
+ """An organization-scoped audit log export destination.
+
+ Delivery is at-least-once for rows visible when their window is committed: a delivery that is retried rewrites the same object, and the same `event_id` can appear in more than one object, so consumers must deduplicate on `event_id`. Each event-time window is held for ten minutes before it commits; a row that becomes visible after its window is committed may not be delivered.
+
+ Objects are written as `/destination_id=/org_id=/date=/hour=/-.jsonl.gz`, where `date` and `hour` are the UTC calendar hour that fully contains every row in the object, so the layout is safe to register as a Hive-partitioned table. The object name is derived from the rows it holds, so a retried delivery rewrites its own object.
+ """
+
+ id: str
+
+ bucket: str
+
+ consecutive_failures: int
+
+ created_at: datetime
+
+ external_id: str
+
+ format: Literal["jsonl.gz"]
+
+ kernel_role_arn: str
+ """The Kernel role that assumes `role_arn` in your account to deliver logs.
+
+ Allow this role as the principal in your role's trust policy, and require
+ `external_id` as the `sts:ExternalId` condition.
+
+ Recreating a destination issues a new `external_id`, which the trust policy has
+ to be updated to match.
+ """
+
+ prefix: str
+
+ region: str
+
+ role_arn: str
+
+ status: Literal["active", "paused"]
+ """Pausing prevents new delivery attempts.
+
+ An S3 upload already in progress may complete after the pause response; its rows
+ can appear again after the destination is resumed.
+ """
+
+ type: Literal["s3"]
+
+ updated_at: datetime
+
+ kms_key_id: Optional[str] = None
+
+ last_error: Optional[str] = None
+ """Sanitized description of the most recent delivery failure."""
+
+ last_error_at: Optional[datetime] = None
+
+ last_exported_cursor: Optional[str] = None
+ """Opaque, versioned checkpoint for forward-only continuous export.
+
+ This value is not compatible with audit-log list page tokens.
+
+ Delivery starts at the moment the destination is activated, so events recorded
+ before that are not delivered. Pausing stops delivery and resuming starts again
+ from the time of the resume: events recorded while a destination was paused are
+ never exported, and pausing is not a way to defer delivery.
+ """
+
+ last_success_at: Optional[datetime] = None
+
+ next_attempt_at: Optional[datetime] = None
diff --git a/src/kernel/types/audit_logs/audit_log_export_destination_test_result.py b/src/kernel/types/audit_logs/audit_log_export_destination_test_result.py
new file mode 100644
index 00000000..ca1eda6f
--- /dev/null
+++ b/src/kernel/types/audit_logs/audit_log_export_destination_test_result.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["AuditLogExportDestinationTestResult", "Error"]
+
+
+class Error(BaseModel):
+ code: Literal["assume_role_failed", "put_object_failed"]
+
+ message: str
+
+
+class AuditLogExportDestinationTestResult(BaseModel):
+ stage: Literal["assume_role", "put_object", "complete"]
+
+ success: bool
+
+ error: Optional[Error] = None
diff --git a/src/kernel/types/audit_logs/export_destination_create_params.py b/src/kernel/types/audit_logs/export_destination_create_params.py
new file mode 100644
index 00000000..b01cdee0
--- /dev/null
+++ b/src/kernel/types/audit_logs/export_destination_create_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ExportDestinationCreateParams"]
+
+
+class ExportDestinationCreateParams(TypedDict, total=False):
+ bucket: Required[str]
+
+ format: Required[Literal["jsonl.gz"]]
+
+ prefix: Required[str]
+
+ region: Required[str]
+
+ role_arn: Required[str]
+
+ type: Required[Literal["s3"]]
+
+ kms_key_id: str
diff --git a/src/kernel/types/audit_logs/export_destination_list_params.py b/src/kernel/types/audit_logs/export_destination_list_params.py
new file mode 100644
index 00000000..2026a4b1
--- /dev/null
+++ b/src/kernel/types/audit_logs/export_destination_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__ = ["ExportDestinationListParams"]
+
+
+class ExportDestinationListParams(TypedDict, total=False):
+ limit: int
+ """Limit the number of destinations to return."""
+
+ offset: int
+ """Offset the number of destinations to return."""
diff --git a/src/kernel/types/audit_logs/export_destination_update_params.py b/src/kernel/types/audit_logs/export_destination_update_params.py
new file mode 100644
index 00000000..e21538f0
--- /dev/null
+++ b/src/kernel/types/audit_logs/export_destination_update_params.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ExportDestinationUpdateParams"]
+
+
+class ExportDestinationUpdateParams(TypedDict, total=False):
+ bucket: str
+
+ kms_key_id: str
+ """KMS key ID, alias, or ARN.
+
+ Set to an empty string to remove the configured KMS key; omit or send null to
+ leave unchanged.
+ """
+
+ prefix: str
+
+ region: str
+
+ role_arn: str
+
+ status: Literal["active", "paused"]
diff --git a/src/kernel/types/auth/__init__.py b/src/kernel/types/auth/__init__.py
index db897944..ced593b0 100644
--- a/src/kernel/types/auth/__init__.py
+++ b/src/kernel/types/auth/__init__.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+from .auth_context import AuthContext as AuthContext
from .managed_auth import ManagedAuth as ManagedAuth
from .login_response import LoginResponse as LoginResponse
from .connection_list_params import ConnectionListParams as ConnectionListParams
@@ -11,3 +12,7 @@
from .connection_submit_params import ConnectionSubmitParams as ConnectionSubmitParams
from .connection_update_params import ConnectionUpdateParams as ConnectionUpdateParams
from .connection_follow_response import ConnectionFollowResponse as ConnectionFollowResponse
+from .connection_timeline_params import ConnectionTimelineParams as ConnectionTimelineParams
+from .managed_auth_browser_config import ManagedAuthBrowserConfig as ManagedAuthBrowserConfig
+from .managed_auth_timeline_event import ManagedAuthTimelineEvent as ManagedAuthTimelineEvent
+from .managed_auth_browser_config_param import ManagedAuthBrowserConfigParam as ManagedAuthBrowserConfigParam
diff --git a/src/kernel/types/auth/auth_context.py b/src/kernel/types/auth/auth_context.py
new file mode 100644
index 00000000..650d63c5
--- /dev/null
+++ b/src/kernel/types/auth/auth_context.py
@@ -0,0 +1,98 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = [
+ "AuthContext",
+ "Authentication",
+ "Authorization",
+ "AuthorizationCredentialScope",
+ "AuthorizationEffectiveScope",
+ "Organization",
+ "Principal",
+]
+
+
+class Authentication(BaseModel):
+ credential_id: Optional[str] = None
+ """
+ The API key ID when authenticated with an API key; null for session credentials.
+ """
+
+ method: Literal["api_key", "jwt"]
+ """The credential format used to authenticate the request."""
+
+ source: Literal["api_key", "oauth", "dashboard"]
+ """The source classification resolved by authentication middleware."""
+
+
+class AuthorizationCredentialScope(BaseModel):
+ """A scope within the authenticated organization.
+
+ A null project_id represents organization-wide scope.
+ """
+
+ project_id: Optional[str] = None
+ """The Kernel project ID, or null when the scope is organization-wide."""
+
+
+class AuthorizationEffectiveScope(BaseModel):
+ """A scope within the authenticated organization.
+
+ A null project_id represents organization-wide scope.
+ """
+
+ project_id: Optional[str] = None
+ """The Kernel project ID, or null when the scope is organization-wide."""
+
+
+class Authorization(BaseModel):
+ """The credential's maximum scope and the effective scope selected for this request.
+
+ Future permission data can be added without changing scope semantics.
+ """
+
+ credential_scope: AuthorizationCredentialScope
+ """A scope within the authenticated organization.
+
+ A null project_id represents organization-wide scope.
+ """
+
+ effective_scope: AuthorizationEffectiveScope
+ """A scope within the authenticated organization.
+
+ A null project_id represents organization-wide scope.
+ """
+
+
+class Organization(BaseModel):
+ id: str
+ """The authenticated Kernel organization ID."""
+
+
+class Principal(BaseModel):
+ id: str
+ """The API key ID for API-key principals or user ID for user principals."""
+
+ type: Literal["api_key", "user"]
+ """The kind of principal authenticated for the request."""
+
+
+class AuthContext(BaseModel):
+ """The identity and authorization context resolved for the current request."""
+
+ authentication: Authentication
+
+ authorization: Authorization
+ """The credential's maximum scope and the effective scope selected for this
+ request.
+
+ Future permission data can be added without changing scope semantics.
+ """
+
+ organization: Organization
+
+ principal: Principal
diff --git a/src/kernel/types/auth/connection_create_params.py b/src/kernel/types/auth/connection_create_params.py
index bdd22681..bf0ac55b 100644
--- a/src/kernel/types/auth/connection_create_params.py
+++ b/src/kernel/types/auth/connection_create_params.py
@@ -2,11 +2,23 @@
from __future__ import annotations
+from typing import Optional
from typing_extensions import Required, TypedDict
from ..._types import SequenceNotStr
+from .managed_auth_browser_config_param import ManagedAuthBrowserConfigParam
+from ..browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam
-__all__ = ["ConnectionCreateParams", "Credential", "Proxy"]
+__all__ = [
+ "ConnectionCreateParams",
+ "BrowserTelemetry",
+ "BrowserTelemetryExport",
+ "BrowserTelemetryExportOtlp",
+ "BrowserTelemetryExportOtlpDestination",
+ "BrowserTelemetryStorage",
+ "Credential",
+ "Proxy",
+]
class ConnectionCreateParams(TypedDict, total=False):
@@ -20,9 +32,11 @@ class ConnectionCreateParams(TypedDict, total=False):
"""
allowed_domains: SequenceNotStr[str]
- """Additional domains valid for this auth flow (besides the primary domain).
+ """Additional hostname roots valid for this auth flow, besides the primary domain.
- Useful when login pages redirect to different domains.
+ Each value allows credential entry on that exact hostname and its subdomains.
+ Leading `www.` and `*.` labels are normalized away. When omitted or empty,
+ credential entry is unrestricted.
The following SSO/OAuth provider domains are automatically allowed by default
and do not need to be specified:
@@ -40,6 +54,30 @@ class ConnectionCreateParams(TypedDict, total=False):
- Ping Identity: _.pingone.com, _.pingidentity.com
"""
+ auto_reauth: bool
+ """
+ Whether to permit automatic re-authentication when a scheduled health check
+ detects an expired session. This is an opt-in flag only — it does not check
+ whether re-auth is actually feasible. Even when true, re-auth only runs when the
+ system has what it needs to perform it (for example, saved credentials for the
+ required login fields), and only after a scheduled health check detects an
+ expired session — so this flag has no effect when `health_checks` is false. When
+ false, expired sessions are marked as `NEEDS_AUTH` instead of attempting
+ re-auth. Defaults to true.
+ """
+
+ browser: ManagedAuthBrowserConfigParam
+ """
+ Default browser configuration for login, reauthentication, and health-check
+ sessions.
+ """
+
+ browser_telemetry: Optional[BrowserTelemetry]
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
+ """
+
credential: Credential
"""Reference to credentials for the auth connection. Use one of:
@@ -53,18 +91,24 @@ class ConnectionCreateParams(TypedDict, total=False):
When set, the system periodically verifies the authentication status and
triggers re-authentication if needed. Maximum is 86400 (24 hours). Default is
- 3600 (1 hour). The minimum depends on your plan: Enterprise: 300 (5 minutes),
- Startup: 1200 (20 minutes), Hobbyist: 3600 (1 hour).
+ 3600 (1 hour) or your plan minimum, whichever is larger. The minimum depends on
+ your plan: Enterprise: 300 (5 minutes), Startup: 1200 (20 minutes), Hobbyist:
+ 3600 (1 hour), Free: 21600 (6 hours).
+ """
+
+ health_checks: bool
+ """Whether to enable periodic health checks.
+
+ When false, the system will not automatically verify authentication status, and
+ `auto_reauth` has no effect on the automatic flow (since re-auth is only
+ triggered by a failed scheduled health check). Defaults to true.
"""
login_url: str
"""Optional login page URL to skip discovery"""
proxy: Proxy
- """Proxy selection.
-
- Provide either id or name. The proxy must belong to the caller's org.
- """
+ """Deprecated. Use browser.proxy. Retained during migration for existing clients."""
record_session: bool
"""Whether to record browser sessions for this connection by default.
@@ -79,6 +123,104 @@ class ConnectionCreateParams(TypedDict, total=False):
"""
+class BrowserTelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class BrowserTelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: BrowserTelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class BrowserTelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: BrowserTelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class BrowserTelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class BrowserTelemetry(TypedDict, total=False):
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
+ """
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: BrowserTelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: BrowserTelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+
class Credential(TypedDict, total=False):
"""Reference to credentials for the auth connection.
@@ -102,10 +244,7 @@ class Credential(TypedDict, total=False):
class Proxy(TypedDict, total=False):
- """Proxy selection.
-
- Provide either id or name. The proxy must belong to the caller's org.
- """
+ """Deprecated. Use browser.proxy. Retained during migration for existing clients."""
id: str
"""Proxy ID"""
diff --git a/src/kernel/types/auth/connection_follow_response.py b/src/kernel/types/auth/connection_follow_response.py
index 1cd1a141..c7690794 100644
--- a/src/kernel/types/auth/connection_follow_response.py
+++ b/src/kernel/types/auth/connection_follow_response.py
@@ -12,13 +12,51 @@
__all__ = [
"ConnectionFollowResponse",
"ManagedAuthStateEvent",
+ "ManagedAuthStateEventChoice",
"ManagedAuthStateEventDiscoveredField",
+ "ManagedAuthStateEventField",
"ManagedAuthStateEventMfaOption",
"ManagedAuthStateEventPendingSSOButton",
"ManagedAuthStateEventSignInOption",
]
+class ManagedAuthStateEventChoice(BaseModel):
+ """Canonical auth-flow choice awaiting user selection."""
+
+ id: str
+ """Stable choice identifier for canonical submit."""
+
+ label: str
+ """Human-readable choice label."""
+
+ type: Literal[
+ "mfa_method", "sso_provider", "sign_in_method", "auth_method", "identifier_method", "account", "other"
+ ]
+ """Choice type."""
+
+ context: Optional[str] = None
+ """Context captured for a choice."""
+
+ description: Optional[str] = None
+ """Additional context for the choice."""
+
+ display_text: Optional[str] = None
+ """Display text captured for a choice."""
+
+ masked_destination: Optional[str] = None
+ """Masked phone number or email address shown for an MFA choice."""
+
+ mfa_type: Optional[Literal["sms", "call", "email", "totp", "push", "password", "passkey", "switch", "other"]] = None
+ """Semantic MFA method.
+
+ Choice id remains the stable identity of the exact option selected.
+ """
+
+ observed_selector: Optional[str] = None
+ """Selector for the visible choice, when available."""
+
+
class ManagedAuthStateEventDiscoveredField(BaseModel):
"""A discovered form field"""
@@ -53,6 +91,37 @@ class ManagedAuthStateEventDiscoveredField(BaseModel):
"""Whether field is required"""
+class ManagedAuthStateEventField(BaseModel):
+ """Canonical field awaiting user input."""
+
+ id: str
+ """Stable field identifier for canonical submit."""
+
+ reason: Literal["missing", "rejected"]
+ """Why the field requires user input."""
+
+ ref: str
+ """Credential reference name to store the submitted value under."""
+
+ type: Literal["identifier", "password", "code", "totp_code", "totp_secret", "text"]
+ """Managed-auth field type."""
+
+ hint: Optional[str] = None
+ """Context shown near the field, including a masked code destination."""
+
+ input_mode: Optional[Literal["text", "email", "tel", "numeric"]] = None
+ """Virtual keyboard hint, independent of field type and browser validation."""
+
+ label: Optional[str] = None
+ """Human-readable label shown to the user."""
+
+ observed_selector: Optional[str] = None
+ """Selector for the visible field, when available."""
+
+ required: Optional[bool] = None
+ """Whether this field is required."""
+
+
class ManagedAuthStateEventMfaOption(BaseModel):
"""An MFA method option for verification"""
@@ -118,6 +187,13 @@ class ManagedAuthStateEvent(BaseModel):
timestamp: datetime
"""Time the state was reported."""
+ choices: Optional[List[ManagedAuthStateEventChoice]] = None
+ """Canonical choices awaiting selection.
+
+ Prefer this over pending_sso_buttons, mfa_options, and sign_in_options when
+ present.
+ """
+
discovered_fields: Optional[List[ManagedAuthStateEventDiscoveredField]] = None
"""
Fields awaiting input (present when flow_step=AWAITING_INPUT; may also be
@@ -136,12 +212,25 @@ class ManagedAuthStateEvent(BaseModel):
flow_step=AWAITING_EXTERNAL_ACTION).
"""
+ fields: Optional[List[ManagedAuthStateEventField]] = None
+ """Canonical fields awaiting input.
+
+ Prefer this over discovered_fields when present.
+ """
+
flow_type: Optional[Literal["LOGIN", "REAUTH"]] = None
"""Type of the current flow."""
hosted_url: Optional[str] = None
"""URL to redirect user to for hosted login."""
+ interaction_id: Optional[str] = None
+ """Opaque identifier for the current canonical interaction.
+
+ Required when submitting fields or choices and changes for each new actionable
+ pause.
+ """
+
live_view_url: Optional[str] = None
"""Browser live view URL for debugging."""
diff --git a/src/kernel/types/auth/connection_list_params.py b/src/kernel/types/auth/connection_list_params.py
index dd8d0de2..dd6ca9f2 100644
--- a/src/kernel/types/auth/connection_list_params.py
+++ b/src/kernel/types/auth/connection_list_params.py
@@ -19,3 +19,6 @@ class ConnectionListParams(TypedDict, total=False):
profile_name: str
"""Filter by profile name"""
+
+ query: str
+ """Search auth connections by ID, domain, or profile name."""
diff --git a/src/kernel/types/auth/connection_login_params.py b/src/kernel/types/auth/connection_login_params.py
index 39756cb1..19e452c9 100644
--- a/src/kernel/types/auth/connection_login_params.py
+++ b/src/kernel/types/auth/connection_login_params.py
@@ -2,31 +2,154 @@
from __future__ import annotations
-from typing_extensions import TypedDict
+from typing import Optional
+from typing_extensions import Literal, TypedDict
-__all__ = ["ConnectionLoginParams", "Proxy"]
+from .managed_auth_browser_config_param import ManagedAuthBrowserConfigParam
+from ..browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam
+
+__all__ = [
+ "ConnectionLoginParams",
+ "BrowserTelemetry",
+ "BrowserTelemetryExport",
+ "BrowserTelemetryExportOtlp",
+ "BrowserTelemetryExportOtlpDestination",
+ "BrowserTelemetryStorage",
+ "Proxy",
+]
class ConnectionLoginParams(TypedDict, total=False):
- proxy: Proxy
- """Proxy selection.
+ browser: ManagedAuthBrowserConfigParam
+ """Browser configuration override for this login.
- Provide either id or name. The proxy must belong to the caller's org.
+ Omitted properties inherit the connection defaults.
"""
+ browser_telemetry: Optional[BrowserTelemetry]
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
+ """
+
+ proxy: Proxy
+ """Deprecated. Use browser.proxy. Retained during migration for existing clients."""
+
record_session: bool
"""Override the connection's default for recording this login's browser session.
When omitted, the connection's record_session default is used.
"""
+ skill_mode: Literal["enabled", "disabled"]
+ """Controls whether this login reads and writes learned domain skills.
-class Proxy(TypedDict, total=False):
- """Proxy selection.
+ Automatic reauths inherit the selected mode until a later accepted login sets
+ enabled or omits this field. Defaults to enabled when omitted.
+ """
+
+
+class BrowserTelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class BrowserTelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: BrowserTelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
- Provide either id or name. The proxy must belong to the caller's org.
+
+class BrowserTelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: BrowserTelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class BrowserTelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class BrowserTelemetry(TypedDict, total=False):
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
"""
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: BrowserTelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: BrowserTelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+
+class Proxy(TypedDict, total=False):
+ """Deprecated. Use browser.proxy. Retained during migration for existing clients."""
+
id: str
"""Proxy ID"""
diff --git a/src/kernel/types/auth/connection_submit_params.py b/src/kernel/types/auth/connection_submit_params.py
index f785b856..04421fef 100644
--- a/src/kernel/types/auth/connection_submit_params.py
+++ b/src/kernel/types/auth/connection_submit_params.py
@@ -9,12 +9,24 @@
class ConnectionSubmitParams(TypedDict, total=False):
+ field_values: Dict[str, str]
+ """Canonical map of field ID to submitted value."""
+
fields: Dict[str, str]
"""Map of field name to value"""
+ interaction_id: str
+ """Opaque interaction ID returned with canonical fields and choices.
+
+ Required for canonical submissions.
+ """
+
mfa_option_id: str
"""The MFA method type to select (when mfa_options were returned)"""
+ selected_choice_id: str
+ """Canonical choice ID selected by the user."""
+
sign_in_option_id: str
"""The sign-in option ID to select (when sign_in_options were returned)"""
diff --git a/src/kernel/types/auth/connection_timeline_params.py b/src/kernel/types/auth/connection_timeline_params.py
new file mode 100644
index 00000000..79c4f5dc
--- /dev/null
+++ b/src/kernel/types/auth/connection_timeline_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ConnectionTimelineParams"]
+
+
+class ConnectionTimelineParams(TypedDict, total=False):
+ limit: int
+ """Maximum number of events to return"""
+
+ offset: int
+ """Number of events to skip"""
+
+ type: Literal["login", "reauth", "health_check"]
+ """Filter the timeline to a single event type."""
diff --git a/src/kernel/types/auth/connection_update_params.py b/src/kernel/types/auth/connection_update_params.py
index 23832778..36a9a9f8 100644
--- a/src/kernel/types/auth/connection_update_params.py
+++ b/src/kernel/types/auth/connection_update_params.py
@@ -2,16 +2,57 @@
from __future__ import annotations
+from typing import Optional
from typing_extensions import TypedDict
from ..._types import SequenceNotStr
+from .managed_auth_browser_config_param import ManagedAuthBrowserConfigParam
+from ..browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam
-__all__ = ["ConnectionUpdateParams", "Credential", "Proxy"]
+__all__ = [
+ "ConnectionUpdateParams",
+ "BrowserTelemetry",
+ "BrowserTelemetryExport",
+ "BrowserTelemetryExportOtlp",
+ "BrowserTelemetryExportOtlpDestination",
+ "BrowserTelemetryStorage",
+ "Credential",
+ "Proxy",
+]
class ConnectionUpdateParams(TypedDict, total=False):
allowed_domains: SequenceNotStr[str]
- """Additional domains valid for this auth flow (replaces existing list)"""
+ """Additional hostname roots valid for this auth flow.
+
+ Each value allows credential entry on that exact hostname and its subdomains;
+ leading `www.` and `*.` labels are normalized away. An empty list leaves
+ credential entry unrestricted. Replaces the existing list.
+ """
+
+ auto_reauth: bool
+ """Whether automatic re-authentication is permitted for this connection.
+
+ This is an opt-in flag only — it does not check whether re-auth is actually
+ feasible. Even when true, re-auth only runs when the system has what it needs to
+ perform it (for example, saved credentials for the required login fields), and
+ only after a scheduled health check detects an expired session — so this flag
+ has no effect when `health_checks` is false. When false, expired sessions
+ detected by a health check are marked as `NEEDS_AUTH` instead of attempting
+ re-auth.
+ """
+
+ browser: ManagedAuthBrowserConfigParam
+ """
+ Browser configuration updates for future login, reauthentication, and
+ health-check sessions. Omitted properties remain unchanged.
+ """
+
+ browser_telemetry: Optional[BrowserTelemetry]
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
+ """
credential: Credential
"""Reference to credentials for the auth connection. Use one of:
@@ -24,14 +65,19 @@ class ConnectionUpdateParams(TypedDict, total=False):
health_check_interval: int
"""Interval in seconds between automatic health checks"""
+ health_checks: bool
+ """Whether periodic health checks are enabled.
+
+ When set to false, the system will not automatically verify authentication
+ status, and `auto_reauth` has no effect on the automatic flow (since re-auth is
+ only triggered by a failed scheduled health check).
+ """
+
login_url: str
"""Login page URL. Set to empty string to clear."""
proxy: Proxy
- """Proxy selection.
-
- Provide either id or name. The proxy must belong to the caller's org.
- """
+ """Deprecated. Use browser.proxy. Retained during migration for existing clients."""
record_session: bool
"""Whether to record browser sessions for this connection by default"""
@@ -40,6 +86,104 @@ class ConnectionUpdateParams(TypedDict, total=False):
"""Whether to save credentials after every successful login"""
+class BrowserTelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class BrowserTelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: BrowserTelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class BrowserTelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: BrowserTelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class BrowserTelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class BrowserTelemetry(TypedDict, total=False):
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
+ """
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: BrowserTelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: BrowserTelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+
class Credential(TypedDict, total=False):
"""Reference to credentials for the auth connection.
@@ -63,10 +207,7 @@ class Credential(TypedDict, total=False):
class Proxy(TypedDict, total=False):
- """Proxy selection.
-
- Provide either id or name. The proxy must belong to the caller's org.
- """
+ """Deprecated. Use browser.proxy. Retained during migration for existing clients."""
id: str
"""Proxy ID"""
diff --git a/src/kernel/types/auth/managed_auth.py b/src/kernel/types/auth/managed_auth.py
index 980d0207..aab06668 100644
--- a/src/kernel/types/auth/managed_auth.py
+++ b/src/kernel/types/auth/managed_auth.py
@@ -5,8 +5,158 @@
from typing_extensions import Literal
from ..._models import BaseModel
+from .managed_auth_browser_config import ManagedAuthBrowserConfig
+from ..browsers.browser_telemetry_categories_config import BrowserTelemetryCategoriesConfig
-__all__ = ["ManagedAuth", "Credential", "DiscoveredField", "MfaOption", "PendingSSOButton", "SignInOption"]
+__all__ = [
+ "ManagedAuth",
+ "BrowserTelemetry",
+ "BrowserTelemetryExport",
+ "BrowserTelemetryExportOtlp",
+ "BrowserTelemetryExportOtlpDestination",
+ "BrowserTelemetryStorage",
+ "Choice",
+ "Credential",
+ "DiscoveredField",
+ "Field",
+ "MfaOption",
+ "PendingSSOButton",
+ "SignInOption",
+]
+
+
+class BrowserTelemetryExportOtlpDestination(BaseModel):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: Optional[str] = None
+ """OTLP destination ID"""
+
+ name: Optional[str] = None
+ """OTLP destination name"""
+
+
+class BrowserTelemetryExportOtlp(BaseModel):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: Optional[BrowserTelemetryExportOtlpDestination] = None
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: Optional[bool] = None
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class BrowserTelemetryExport(BaseModel):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: Optional[BrowserTelemetryExportOtlp] = None
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class BrowserTelemetryStorage(BaseModel):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: Optional[bool] = None
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class BrowserTelemetry(BaseModel):
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
+ """
+
+ browser: Optional[BrowserTelemetryCategoriesConfig] = None
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: Optional[bool] = None
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: Optional[BrowserTelemetryExport] = None
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: Optional[BrowserTelemetryStorage] = None
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+
+class Choice(BaseModel):
+ """Canonical auth-flow choice awaiting user selection."""
+
+ id: str
+ """Stable choice identifier for canonical submit."""
+
+ label: str
+ """Human-readable choice label."""
+
+ type: Literal[
+ "mfa_method", "sso_provider", "sign_in_method", "auth_method", "identifier_method", "account", "other"
+ ]
+ """Choice type."""
+
+ context: Optional[str] = None
+ """Context captured for a choice."""
+
+ description: Optional[str] = None
+ """Additional context for the choice."""
+
+ display_text: Optional[str] = None
+ """Display text captured for a choice."""
+
+ masked_destination: Optional[str] = None
+ """Masked phone number or email address shown for an MFA choice."""
+
+ mfa_type: Optional[Literal["sms", "call", "email", "totp", "push", "password", "passkey", "switch", "other"]] = None
+ """Semantic MFA method.
+
+ Choice id remains the stable identity of the exact option selected.
+ """
+
+ observed_selector: Optional[str] = None
+ """Selector for the visible choice, when available."""
class Credential(BaseModel):
@@ -65,6 +215,37 @@ class DiscoveredField(BaseModel):
"""Whether field is required"""
+class Field(BaseModel):
+ """Canonical field awaiting user input."""
+
+ id: str
+ """Stable field identifier for canonical submit."""
+
+ reason: Literal["missing", "rejected"]
+ """Why the field requires user input."""
+
+ ref: str
+ """Credential reference name to store the submitted value under."""
+
+ type: Literal["identifier", "password", "code", "totp_code", "totp_secret", "text"]
+ """Managed-auth field type."""
+
+ hint: Optional[str] = None
+ """Context shown near the field, including a masked code destination."""
+
+ input_mode: Optional[Literal["text", "email", "tel", "numeric"]] = None
+ """Virtual keyboard hint, independent of field type and browser validation."""
+
+ label: Optional[str] = None
+ """Human-readable label shown to the user."""
+
+ observed_selector: Optional[str] = None
+ """Selector for the visible field, when available."""
+
+ required: Optional[bool] = None
+ """Whether this field is required."""
+
+
class MfaOption(BaseModel):
"""An MFA method option for verification"""
@@ -143,12 +324,18 @@ class ManagedAuth(BaseModel):
"""
status: Literal["AUTHENTICATED", "NEEDS_AUTH"]
- """Current authentication status of the managed profile"""
+ """Last known authentication status of the managed profile.
- allowed_domains: Optional[List[str]] = None
+ An inconclusive health check preserves this status and does not verify the
+ current session.
"""
- Additional domains that are valid for this auth flow (besides the primary
- domain). Useful when login pages redirect to different domains.
+
+ allowed_domains: Optional[List[str]] = None
+ """Additional hostname roots valid for this auth flow, besides the primary domain.
+
+ Each value allows credential entry on that exact hostname and its subdomains.
+ Leading `www.` and `*.` labels are normalized away. When omitted or empty,
+ credential entry is unrestricted.
The following SSO/OAuth provider domains are automatically allowed by default
and do not need to be specified:
@@ -166,6 +353,24 @@ class ManagedAuth(BaseModel):
- Ping Identity: _.pingone.com, _.pingidentity.com
"""
+ auto_reauth: Optional[bool] = None
+ """Whether automatic re-authentication is permitted for this connection.
+
+ This is an opt-in flag only — it does not check whether re-auth is actually
+ feasible. Even when true, re-auth only runs when the system has what it needs to
+ perform it (for example, saved credentials for the required login fields), and
+ only after a scheduled health check detects an expired session — so this flag
+ has no effect when `health_checks` is false. When false, expired sessions
+ detected by a health check are marked as `NEEDS_AUTH` instead of attempting
+ re-auth.
+ """
+
+ browser: Optional[ManagedAuthBrowserConfig] = None
+ """
+ Default browser configuration for login, reauthentication, and health-check
+ sessions.
+ """
+
browser_session_id: Optional[str] = None
"""
ID of the underlying browser session driving the current flow (present when flow
@@ -173,14 +378,89 @@ class ManagedAuth(BaseModel):
`/browsers` API.
"""
+ browser_telemetry: Optional[BrowserTelemetry] = None
+ """Deprecated.
+
+ Use browser.telemetry. Retained during migration for existing clients.
+ """
+
can_reauth: Optional[bool] = None
"""
- Whether automatic re-authentication is possible (has credential, selectors, and
- login_url)
+ Whether this connection's stored requirements are eligible for unattended
+ re-authentication. A true value can represent either fully satisfiable
+ requirements or a best-effort attempt. It does not account for whether automatic
+ re-authentication is enabled or parent workflow state such as an active flow or
+ circuit-breaker cooldown, so it does not guarantee an attempt on the next health
+ check. See `can_reauth_reason` for the specific outcome.
+ """
+
+ can_reauth_reason: Optional[
+ Literal[
+ "external_credential",
+ "cua_has_credential",
+ "has_credential",
+ "viable_plans_found",
+ "no_requirements_recorded",
+ "totp_reauth_allowed",
+ "optimistic_totp_attempt",
+ "requirements_satisfiable",
+ "no_prior_successful_login",
+ "no_credential",
+ "no_viable_plans",
+ "viable_plans_require_external_action",
+ "requires_external_action",
+ "requires_totp_without_secret",
+ "requires_sms_code",
+ "requires_email_code",
+ "requires_customer_input",
+ ]
+ ] = None
+ """
+ Machine-readable reason for the current value of `can_reauth`. Affirmative
+ values (requirements are eligible for unattended re-authentication):
+
+ - `external_credential` — an external credential provider is attached
+ - `cua_has_credential` — CUA flow with a stored credential
+ - `has_credential` — Kernel credential is attached (optimistic; plan viability
+ not checked)
+ - `viable_plans_found` — at least one stored login plan can be replayed
+ - `no_requirements_recorded` — no recorded credential requirements to fail
+ against
+ - `totp_reauth_allowed` — TOTP is the only recorded requirement and a stored
+ secret can generate the code
+ - `optimistic_totp_attempt` — TOTP was previously required but no reusable
+ secret is stored; the connection remains eligible for a
+ circuit-breaker-bounded attempt because the site may not challenge returning
+ sessions
+ - `requirements_satisfiable` — recorded requirements contain no recognized
+ blocker
+
+ Negative values (a human must complete the login flow):
+
+ - `no_prior_successful_login` — connection has never completed a successful
+ login
+ - `no_credential` — no Kernel or external credential attached
+ - `no_viable_plans` — credential attached but no replayable login plan exists
+ yet
+ - `viable_plans_require_external_action` — stored plans need an external step
+ (email link, push, etc.)
+ - `requires_external_action` — recorded requirements include an external step
+ - `requires_totp_without_secret` — flow needs a TOTP code but no TOTP secret is
+ stored
+ - `requires_sms_code` — flow needs an SMS code that cannot be received
+ automatically
+ - `requires_email_code` — flow needs an email code that cannot be received
+ automatically
+ - `requires_customer_input` — flow needs another field or choice that is
+ unavailable during unattended re-authentication
+ """
+
+ choices: Optional[List[Choice]] = None
+ """Canonical choices awaiting selection.
+
+ Prefer this over pending_sso_buttons, mfa_options, and sign_in_options when
+ present.
"""
-
- can_reauth_reason: Optional[str] = None
- """Reason why automatic re-authentication is or is not possible"""
credential: Optional[Credential] = None
"""Reference to credentials for the auth connection. Use one of:
@@ -208,6 +488,12 @@ class ManagedAuth(BaseModel):
flow_step=awaiting_external_action)
"""
+ fields: Optional[List[Field]] = None
+ """Canonical fields awaiting input.
+
+ Prefer this over discovered_fields when present.
+ """
+
flow_expires_at: Optional[datetime] = None
"""When the current flow expires (null when no flow in progress).
@@ -232,13 +518,37 @@ class ManagedAuth(BaseModel):
When set, the system periodically verifies the authentication status and
triggers re-authentication if needed. Maximum is 86400 (24 hours). Default is
- 3600 (1 hour). The minimum depends on your plan: Enterprise: 300 (5 minutes),
- Startup: 1200 (20 minutes), Hobbyist: 3600 (1 hour).
+ 3600 (1 hour) or your plan minimum, whichever is larger. The minimum depends on
+ your plan: Enterprise: 300 (5 minutes), Startup: 1200 (20 minutes), Hobbyist:
+ 3600 (1 hour), Free: 21600 (6 hours).
+ """
+
+ health_check_unavailable_reason: Optional[Literal["no_auth_check_url"]] = None
+ """Why health checks cannot verify this connection.
+
+ Present when health checks are enabled but no auth check URL is available; a
+ recent last_auth_check_at is not evidence of a valid session.
+ """
+
+ health_checks: Optional[bool] = None
+ """Whether periodic health checks are enabled for this connection.
+
+ When false, the system will not automatically verify authentication status, and
+ `auto_reauth` has no effect on the automatic flow (since re-auth is only
+ triggered by a failed scheduled health check). Manually triggering a health
+ check via the API still works regardless of this setting.
"""
hosted_url: Optional[str] = None
"""URL to redirect user to for hosted login (present when flow in progress)"""
+ interaction_id: Optional[str] = None
+ """Opaque identifier for the current canonical interaction.
+
+ Required when submitting fields or choices and changes for each new actionable
+ pause.
+ """
+
last_auth_at: Optional[datetime] = None
"""Deprecated alias for `last_auth_check_at`.
@@ -277,7 +587,10 @@ class ManagedAuth(BaseModel):
"""URL where the browser landed after successful login"""
proxy_id: Optional[str] = None
- """ID of the proxy associated with this connection, if any."""
+ """Deprecated.
+
+ Read browser.proxy instead. Retained during migration for existing clients.
+ """
sign_in_options: Optional[List[SignInOption]] = None
"""
diff --git a/src/kernel/types/auth/managed_auth_browser_config.py b/src/kernel/types/auth/managed_auth_browser_config.py
new file mode 100644
index 00000000..0de4499d
--- /dev/null
+++ b/src/kernel/types/auth/managed_auth_browser_config.py
@@ -0,0 +1,142 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from ..browser_proxy_config import BrowserProxyConfig
+from ..browsers.browser_telemetry_categories_config import BrowserTelemetryCategoriesConfig
+
+__all__ = [
+ "ManagedAuthBrowserConfig",
+ "Telemetry",
+ "TelemetryExport",
+ "TelemetryExportOtlp",
+ "TelemetryExportOtlpDestination",
+ "TelemetryStorage",
+]
+
+
+class TelemetryExportOtlpDestination(BaseModel):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: Optional[str] = None
+ """OTLP destination ID"""
+
+ name: Optional[str] = None
+ """OTLP destination name"""
+
+
+class TelemetryExportOtlp(BaseModel):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: Optional[TelemetryExportOtlpDestination] = None
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: Optional[bool] = None
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class TelemetryExport(BaseModel):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: Optional[TelemetryExportOtlp] = None
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class TelemetryStorage(BaseModel):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: Optional[bool] = None
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class Telemetry(BaseModel):
+ """Browser telemetry configuration using the same semantics as browser create."""
+
+ browser: Optional[BrowserTelemetryCategoriesConfig] = None
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: Optional[bool] = None
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: Optional[TelemetryExport] = None
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: Optional[TelemetryStorage] = None
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+
+class ManagedAuthBrowserConfig(BaseModel):
+ """
+ Browser configuration applied to browser sessions created for a managed auth connection. Managed auth controls the profile, headless mode, timeout, start URL, kiosk mode, and viewport.
+ """
+
+ proxy: Optional[BrowserProxyConfig] = None
+ """Proxy configuration for managed auth browser sessions.
+
+ Omit on create to derive the default from stealth, or on update and login to
+ preserve or inherit the connection default.
+ """
+
+ region: Optional[Literal["us-east", "us-west", "eu-west", "ap-southeast"]] = None
+ """Browser region.
+
+ Omit on create to use us-east, on update to keep the current region, or on login
+ to inherit it. Login overrides apply only to that login. Non-default regions
+ require an eligible plan and organization access.
+ """
+
+ stealth: Optional[bool] = None
+ """Whether managed auth browser sessions use stealth mode.
+
+ Defaults to true when omitted.
+ """
+
+ telemetry: Optional[Telemetry] = None
+ """Browser telemetry configuration using the same semantics as browser create."""
diff --git a/src/kernel/types/auth/managed_auth_browser_config_param.py b/src/kernel/types/auth/managed_auth_browser_config_param.py
new file mode 100644
index 00000000..98577698
--- /dev/null
+++ b/src/kernel/types/auth/managed_auth_browser_config_param.py
@@ -0,0 +1,143 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal, TypedDict
+
+from ..browser_proxy_config_param import BrowserProxyConfigParam
+from ..browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam
+
+__all__ = [
+ "ManagedAuthBrowserConfigParam",
+ "Telemetry",
+ "TelemetryExport",
+ "TelemetryExportOtlp",
+ "TelemetryExportOtlpDestination",
+ "TelemetryStorage",
+]
+
+
+class TelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class TelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: TelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class TelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: TelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class TelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class Telemetry(TypedDict, total=False):
+ """Browser telemetry configuration using the same semantics as browser create."""
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: TelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: TelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+
+class ManagedAuthBrowserConfigParam(TypedDict, total=False):
+ """
+ Browser configuration applied to browser sessions created for a managed auth connection. Managed auth controls the profile, headless mode, timeout, start URL, kiosk mode, and viewport.
+ """
+
+ proxy: BrowserProxyConfigParam
+ """Proxy configuration for managed auth browser sessions.
+
+ Omit on create to derive the default from stealth, or on update and login to
+ preserve or inherit the connection default.
+ """
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Browser region.
+
+ Omit on create to use us-east, on update to keep the current region, or on login
+ to inherit it. Login overrides apply only to that login. Non-default regions
+ require an eligible plan and organization access.
+ """
+
+ stealth: bool
+ """Whether managed auth browser sessions use stealth mode.
+
+ Defaults to true when omitted.
+ """
+
+ telemetry: Optional[Telemetry]
+ """Browser telemetry configuration using the same semantics as browser create."""
diff --git a/src/kernel/types/auth/managed_auth_timeline_event.py b/src/kernel/types/auth/managed_auth_timeline_event.py
new file mode 100644
index 00000000..79eee70c
--- /dev/null
+++ b/src/kernel/types/auth/managed_auth_timeline_event.py
@@ -0,0 +1,91 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["ManagedAuthTimelineEvent"]
+
+
+class ManagedAuthTimelineEvent(BaseModel):
+ """
+ A single event in an auth connection's history — a login attempt, an automatic re-auth attempt, or a health check.
+ """
+
+ id: str
+ """Identifier of the underlying login/reauth session or health check."""
+
+ status: Literal["IN_PROGRESS", "SUCCESS", "EXPIRED", "CANCELED", "FAILED", "AUTHENTICATED", "NEEDS_AUTH"]
+ """Outcome of the event.
+
+ For login/reauth events this is the flow status (IN_PROGRESS, SUCCESS, EXPIRED,
+ CANCELED, FAILED). For health_check events it is the observed session state
+ (AUTHENTICATED, NEEDS_AUTH).
+ """
+
+ timestamp: datetime
+ """When the event occurred."""
+
+ type: Literal["login", "reauth", "health_check"]
+ """The kind of event.
+
+ "login" and "reauth" are authentication attempts; "health_check" is a periodic
+ session-validity check.
+ """
+
+ browser_session_id: Optional[str] = None
+ """Browser session that produced the event, if one was created."""
+
+ completed_at: Optional[datetime] = None
+ """When the login/reauth attempt first reached a terminal status.
+
+ Stable across retries and subsequent cleanup writes. Absent for in-progress
+ attempts, health checks, and historical attempts without a recorded completion
+ time.
+ """
+
+ error_code: Optional[str] = None
+ """Machine-readable error code. Present when a login/reauth event failed."""
+
+ error_message: Optional[str] = None
+ """Human-readable error message. Present when a login/reauth event failed."""
+
+ previous_status: Optional[Literal["AUTHENTICATED", "NEEDS_AUTH"]] = None
+ """The session state observed before this event.
+
+ Present for health_check events that recorded a prior state.
+ """
+
+ replay_id: Optional[str] = None
+ """
+ Replay recording ID for the event's browser session, if session recording was
+ enabled.
+ """
+
+ step: Optional[
+ Literal[
+ "INITIALIZED",
+ "DISCOVERING",
+ "AWAITING_INPUT",
+ "AWAITING_EXTERNAL_ACTION",
+ "AWAITING_HUMAN_INTERVENTION",
+ "SUBMITTING",
+ "COMPLETED",
+ "EXPIRED",
+ ]
+ ] = None
+ """The step the flow reached. Present for login/reauth events."""
+
+ telemetry_captured: Optional[bool] = None
+ """Whether browser telemetry capture started for this event's browser session."""
+
+ updated_at: Optional[datetime] = None
+ """When the event was last updated. Present for login/reauth events."""
+
+ website_error: Optional[str] = None
+ """Visible error message from the website (e.g., 'Incorrect password').
+
+ Present when the website displayed an error during the attempt.
+ """
diff --git a/src/kernel/types/browser.py b/src/kernel/types/browser.py
new file mode 100644
index 00000000..f7905451
--- /dev/null
+++ b/src/kernel/types/browser.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .shared.browser_viewport import BrowserViewport
+
+__all__ = ["Browser"]
+
+
+class Browser(BaseModel):
+ """Browser settings that can be passed directly to `POST /browsers`."""
+
+ gpu: bool
+
+ headless: bool
+
+ stealth: bool
+
+ viewport: BrowserViewport
+ """
+ Initial browser window size in pixels with optional refresh rate. If omitted,
+ image defaults apply (1920x1080@25). For GPU images, the default is
+ 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
+ """
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_create_params.py b/src/kernel/types/browser_create_params.py
index 1a4493f0..8e319668 100644
--- a/src/kernel/types/browser_create_params.py
+++ b/src/kernel/types/browser_create_params.py
@@ -2,18 +2,40 @@
from __future__ import annotations
-from typing import Iterable
-from typing_extensions import TypedDict
+from typing import Dict, Iterable, Optional
+from typing_extensions import Literal, TypedDict
-from .browser_persistence_param import BrowserPersistenceParam
+from .tags_param import TagsParam
+from .browser_video_memory import BrowserVideoMemory
+from .vault_reference_param import VaultReferenceParam
+from .browser_memory_request import BrowserMemoryRequest
+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
-__all__ = ["BrowserCreateParams"]
+__all__ = [
+ "BrowserCreateParams",
+ "Telemetry",
+ "TelemetryExport",
+ "TelemetryExportOtlp",
+ "TelemetryExportOtlpDestination",
+ "TelemetryStorage",
+]
class BrowserCreateParams(TypedDict, total=False):
+ chrome_policy: Dict[str, object]
+ """Custom Chrome enterprise policy overrides applied to this browser session.
+
+ Keys are Chrome enterprise policy names; values must match their expected types.
+ Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
+ https://chromeenterprise.google/policies/
+ """
+
extensions: Iterable[BrowserExtension]
"""List of browser extensions to load into the session.
@@ -23,7 +45,7 @@ class BrowserCreateParams(TypedDict, total=False):
gpu: bool
"""If true, enables GPU acceleration for the browser session.
- Requires Start-Up or Enterprise plan and headless=false.
+ Requires Start-Up or Enterprise plan, headless=false, and region=us-east.
"""
headless: bool
@@ -41,8 +63,32 @@ class BrowserCreateParams(TypedDict, total=False):
view.
"""
- persistence: BrowserPersistenceParam
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+ 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.
+
+ Headless: 1GiB (default), 2GiB, or 4GiB. Headful: 8GiB (default) or 16GiB.
+ """
+
+ name: str
+ """
+ Optional human-readable name for the browser session, used to find it later in
+ the dashboard. Must be unique among active sessions within the project. Can be
+ changed later via PATCH /browsers/{id_or_name}.
+ """
+
+ network: BrowserNetworkConfigParam
+ """Network configuration for the browser session.
+
+ Only allowed_hosts can be changed after creation, with PATCH
+ /browsers/{id_or_name}.
+ """
profile: BrowserProfile
"""Profile selection for the browser session.
@@ -51,25 +97,62 @@ class BrowserCreateParams(TypedDict, total=False):
into the browser session. Profiles must be created beforehand.
"""
+ proxy: BrowserProxyConfigParam
+ """Proxy configuration for the browser session.
+
+ Cannot be combined with proxy_id. Omit to use the browser default: stealth
+ browsers use Kernel's default stealth proxy, while non-stealth browsers use
+ direct egress. Set mode to direct to force direct egress regardless of stealth.
+ Set mode to default to explicitly use the browser default: Kernel's default
+ stealth proxy when stealth=true, or direct egress when stealth=false. Select id
+ or name to use that proxy regardless of stealth. Proxy selection does not change
+ stealth or CAPTCHA solver behavior.
+ """
+
proxy_id: str
"""Optional proxy to associate to the browser session.
- Must reference a proxy belonging to the caller's org.
+ Must reference a proxy in the same project as the browser session. Deprecated in
+ favor of proxy.
+ """
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region for the browser session.
+
+ It is fixed once the session is created. Region selection requires a Start-Up or
+ Enterprise plan, defaults to us-east when omitted on create.
"""
start_url: str
- """Optional URL to navigate to immediately after the browser is created.
+ """Optional URL to open when the browser session is created.
- Best-effort: failures to navigate do not fail browser creation. Any pre-existing
- tabs are reduced to a single tab which is then navigated. Accepts any URL
- Chromium can resolve, including chrome:// pages. Ignored when reusing an
- existing persistent session.
+ Navigation is best-effort, so navigation failures do not prevent the session
+ from being created.
"""
stealth: bool
+ """If true, launches the browser in stealth mode and enables the CAPTCHA solver.
+
+ Defaults to false. When proxy is omitted, stealth browsers use Kernel's default
+ stealth proxy and non-stealth browsers use direct egress. An explicit proxy
+ configuration changes only egress; it does not enable or disable stealth or the
+ CAPTCHA solver.
"""
- If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
+
+ tags: TagsParam
+ """
+ Optional user-defined key-value tags for the browser session, used to find and
+ group sessions later. Can be changed later via PATCH /browsers/{id_or_name}. Up
+ to 50 pairs.
+ """
+
+ telemetry: Optional[Telemetry]
+ """Telemetry configuration for the browser session.
+
+ Set enabled to true to start capture using VM defaults, or provide browser
+ category settings. If omitted, null, set to an empty object ({}), set to
+ enabled: false without browser category settings, or all four categories are
+ explicitly disabled, capture is not started.
"""
timeout_seconds: int
@@ -81,18 +164,129 @@ class BrowserCreateParams(TypedDict, total=False):
see is +/- 5 seconds around the specified value.
"""
+ vaults: Iterable[VaultReferenceParam]
+ """Project-scoped vaults to link to the browser session.
+
+ Links are immutable after creation.
+ """
+
+ video_memory: BrowserVideoMemory
+ """Video memory (VRAM) for a GPU browser session.
+
+ Requires gpu=true. Defaults to 2GiB, which comes with 4 vCPU and 6GiB memory.
+ 4GiB comes with 8 vCPU and 12GiB memory.
+ """
+
viewport: BrowserViewport
"""
Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
+ """
+
+
+class TelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class TelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
"""
+
+ destination: TelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class TelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: TelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class TelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class Telemetry(TypedDict, total=False):
+ """Telemetry configuration for the browser session.
+
+ Set enabled to true to start capture using VM defaults, or provide browser category settings. If omitted, null, set to an empty object ({}), set to enabled: false without browser category settings, or all four categories are explicitly disabled, capture is not started.
+ """
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: TelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: TelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
diff --git a/src/kernel/types/browser_create_response.py b/src/kernel/types/browser_create_response.py
index e63a6896..53ae17de 100644
--- a/src/kernel/types/browser_create_response.py
+++ b/src/kernel/types/browser_create_response.py
@@ -1,14 +1,22 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, List, Optional
from datetime import datetime
+from typing_extensions import Literal
+from .tags import Tags
from .profile import Profile
from .._models import BaseModel
+from .browser_proxy import BrowserProxy
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_persistence import BrowserPersistence
+from .browser_video_memory import BrowserVideoMemory
+from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
+from .browsers.browser_telemetry_config import BrowserTelemetryConfig
__all__ = ["BrowserCreateResponse"]
@@ -23,6 +31,12 @@ class BrowserCreateResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region of the browser session. Fixed once the session is created."""
+
session_id: str
"""Unique identifier for the browser session"""
@@ -44,6 +58,13 @@ class BrowserCreateResponse(BaseModel):
Only available for non-headless browsers.
"""
+ chrome_policy: Optional[Dict[str, object]] = None
+ """
+ Custom Chrome enterprise policy overrides that were applied to this browser
+ session, if any. Echoed back for verification. Keys are Chrome enterprise policy
+ names.
+ """
+
deleted_at: Optional[datetime] = None
"""When the browser session was soft-deleted. Only present for deleted sessions."""
@@ -56,8 +77,17 @@ class BrowserCreateResponse(BaseModel):
kiosk_mode: Optional[bool] = None
"""Whether the browser session is running in kiosk mode."""
- persistence: Optional[BrowserPersistence] = None
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+ 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."""
+
+ network: Optional[BrowserNetworkConfig] = None
+ """Network configuration the session was created with, if any.
+
+ Omitted when the session has no network configuration.
+ """
pool: Optional[BrowserPoolRef] = None
"""Browser pool this session was acquired from, if any."""
@@ -65,30 +95,66 @@ class BrowserCreateResponse(BaseModel):
profile: Optional[Profile] = None
"""Browser profile metadata."""
+ profile_save_changes: Optional[bool] = None
+ """
+ Whether changes made during this browser session are saved back to its profile
+ when the session ends. Omitted when no profile is attached.
+ """
+
+ proxy: Optional[BrowserProxy] = None
+ """Resolved proxy configuration for this browser session."""
+
proxy_id: Optional[str] = None
- """ID of the proxy associated with this browser session, if any."""
+ """ID of the proxy associated with this browser session, if any.
+
+ Deprecated in favor of proxy.
+ """
start_url: Optional[str] = None
- """URL the session was asked to navigate to on creation, if any.
+ """URL the session was most recently asked to navigate to, if any.
+
+ Recorded for debugging. Navigation is fire-and-forget — the URL is dispatched to
+ the browser without waiting for it to load, and any errors (DNS failure, bad
+ status, timeout) are silently dropped. Captures what was requested, not what the
+ browser actually loaded.
+ """
+
+ tags: Optional[Tags] = None
+ """User-defined key-value tags that were set on this browser session, if any.
- Recorded for debugging — navigation is best-effort and may have failed.
+ Echoed back when present.
"""
+ telemetry: Optional[BrowserTelemetryConfig] = None
+ """Active telemetry configuration for the session, if any."""
+
usage: Optional[BrowserUsage] = None
"""Session usage metrics."""
+ usage_status: Optional[Literal["pending", "ready"]] = None
+ """Whether final usage billing is still pending or complete.
+
+ Only present for deleted sessions.
+ """
+
+ vaults: Optional[List[VaultReference]] = None
+ """Vaults linked when the browser session was created."""
+
+ video_memory: Optional[BrowserVideoMemory] = None
+ """Video memory (VRAM) of the GPU browser session. Only present when gpu is true."""
+
viewport: Optional[BrowserViewport] = None
"""
Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
diff --git a/src/kernel/types/browser_list_params.py b/src/kernel/types/browser_list_params.py
index d659b8cb..5bf673b5 100644
--- a/src/kernel/types/browser_list_params.py
+++ b/src/kernel/types/browser_list_params.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+from typing import Dict
from typing_extensions import Literal, TypedDict
__all__ = ["BrowserListParams"]
@@ -22,7 +23,12 @@ class BrowserListParams(TypedDict, total=False):
"""Number of results to skip. Defaults to 0."""
query: str
- """Search browsers by session ID, profile ID, proxy ID, or pool name."""
+ """
+ Search browsers by name, session ID, profile name or ID, proxy ID, or pool name.
+ """
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Filter sessions by geographic region. Omit to list sessions in all regions."""
status: Literal["active", "deleted", "all"]
"""Filter sessions by status.
@@ -30,3 +36,10 @@ class BrowserListParams(TypedDict, total=False):
"active" returns only active sessions (default), "deleted" returns only
soft-deleted sessions, "all" returns both.
"""
+
+ tags: Dict[str, str]
+ """Filter sessions by tag key-value pairs using deepObject style, e.g.
+
+ ?tags[team]=backend&tags[env]=staging. Multiple pairs are ANDed: a session must
+ match every supplied pair exactly.
+ """
diff --git a/src/kernel/types/browser_list_response.py b/src/kernel/types/browser_list_response.py
index de37e26f..fa6c01ba 100644
--- a/src/kernel/types/browser_list_response.py
+++ b/src/kernel/types/browser_list_response.py
@@ -1,14 +1,22 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, List, Optional
from datetime import datetime
+from typing_extensions import Literal
+from .tags import Tags
from .profile import Profile
from .._models import BaseModel
+from .browser_proxy import BrowserProxy
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_persistence import BrowserPersistence
+from .browser_video_memory import BrowserVideoMemory
+from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
+from .browsers.browser_telemetry_config import BrowserTelemetryConfig
__all__ = ["BrowserListResponse"]
@@ -23,6 +31,12 @@ class BrowserListResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region of the browser session. Fixed once the session is created."""
+
session_id: str
"""Unique identifier for the browser session"""
@@ -44,6 +58,13 @@ class BrowserListResponse(BaseModel):
Only available for non-headless browsers.
"""
+ chrome_policy: Optional[Dict[str, object]] = None
+ """
+ Custom Chrome enterprise policy overrides that were applied to this browser
+ session, if any. Echoed back for verification. Keys are Chrome enterprise policy
+ names.
+ """
+
deleted_at: Optional[datetime] = None
"""When the browser session was soft-deleted. Only present for deleted sessions."""
@@ -56,8 +77,17 @@ class BrowserListResponse(BaseModel):
kiosk_mode: Optional[bool] = None
"""Whether the browser session is running in kiosk mode."""
- persistence: Optional[BrowserPersistence] = None
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+ 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."""
+
+ network: Optional[BrowserNetworkConfig] = None
+ """Network configuration the session was created with, if any.
+
+ Omitted when the session has no network configuration.
+ """
pool: Optional[BrowserPoolRef] = None
"""Browser pool this session was acquired from, if any."""
@@ -65,30 +95,66 @@ class BrowserListResponse(BaseModel):
profile: Optional[Profile] = None
"""Browser profile metadata."""
+ profile_save_changes: Optional[bool] = None
+ """
+ Whether changes made during this browser session are saved back to its profile
+ when the session ends. Omitted when no profile is attached.
+ """
+
+ proxy: Optional[BrowserProxy] = None
+ """Resolved proxy configuration for this browser session."""
+
proxy_id: Optional[str] = None
- """ID of the proxy associated with this browser session, if any."""
+ """ID of the proxy associated with this browser session, if any.
+
+ Deprecated in favor of proxy.
+ """
start_url: Optional[str] = None
- """URL the session was asked to navigate to on creation, if any.
+ """URL the session was most recently asked to navigate to, if any.
+
+ Recorded for debugging. Navigation is fire-and-forget — the URL is dispatched to
+ the browser without waiting for it to load, and any errors (DNS failure, bad
+ status, timeout) are silently dropped. Captures what was requested, not what the
+ browser actually loaded.
+ """
+
+ tags: Optional[Tags] = None
+ """User-defined key-value tags that were set on this browser session, if any.
- Recorded for debugging — navigation is best-effort and may have failed.
+ Echoed back when present.
"""
+ telemetry: Optional[BrowserTelemetryConfig] = None
+ """Active telemetry configuration for the session, if any."""
+
usage: Optional[BrowserUsage] = None
"""Session usage metrics."""
+ usage_status: Optional[Literal["pending", "ready"]] = None
+ """Whether final usage billing is still pending or complete.
+
+ Only present for deleted sessions.
+ """
+
+ vaults: Optional[List[VaultReference]] = None
+ """Vaults linked when the browser session was created."""
+
+ video_memory: Optional[BrowserVideoMemory] = None
+ """Video memory (VRAM) of the GPU browser session. Only present when gpu is true."""
+
viewport: Optional[BrowserViewport] = None
"""
Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
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_memory.py b/src/kernel/types/browser_memory.py
new file mode 100644
index 00000000..ccdb8fd4
--- /dev/null
+++ b/src/kernel/types/browser_memory.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__ = ["BrowserMemory"]
+
+BrowserMemory: TypeAlias = Literal["1GiB", "2GiB", "4GiB", "6GiB", "8GiB", "12GiB", "16GiB"]
diff --git a/src/kernel/types/browser_memory_request.py b/src/kernel/types/browser_memory_request.py
new file mode 100644
index 00000000..8f853631
--- /dev/null
+++ b/src/kernel/types/browser_memory_request.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__ = ["BrowserMemoryRequest"]
+
+BrowserMemoryRequest: TypeAlias = Literal["1GiB", "2GiB", "4GiB", "8GiB", "16GiB"]
diff --git a/src/kernel/types/browser_network_config.py b/src/kernel/types/browser_network_config.py
new file mode 100644
index 00000000..d326c6bf
--- /dev/null
+++ b/src/kernel/types/browser_network_config.py
@@ -0,0 +1,113 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .._models import BaseModel
+from .browser_allowlist_state import BrowserAllowlistState
+
+__all__ = ["BrowserNetworkConfig", "ProxyRoute", "ProxyRouteProxy"]
+
+
+class ProxyRouteProxy(BaseModel):
+ """Select an active non-direct proxy by ID or name. Responses always use ID."""
+
+ id: Optional[str] = None
+
+ name: Optional[str] = None
+
+
+class ProxyRoute(BaseModel):
+ hosts: List[str]
+ """Exact hostnames or leading \\**.
+
+ wildcard patterns (subdomains only); patterns cannot include ports, and matching
+ ignores the destination port. Hosts not matched by any route use the session's
+ top-level proxy (or the browser default when proxy is omitted).
+ """
+
+ proxy: ProxyRouteProxy
+ """Select an active non-direct proxy by ID or name. Responses always use ID."""
+
+
+class BrowserNetworkConfig(BaseModel):
+ """Network configuration for a browser session or browser pool."""
+
+ allowed_hosts: Optional[List[str]] = None
+ """
+ Egress allowlist for a browser session: the only destinations the browser may
+ reach through Kernel-managed egress. Any other destination is refused with a 403
+ whose X-Kernel-Proxy-Error header is network_policy_denied, so pages cannot load
+ or send data to unlisted hosts, including with fetch() and WebSockets. Omit the
+ field for unfiltered egress; an empty list is invalid. The allowlist applies
+ from the browser's first request, and start_url must be allowed by it. Entries
+ are exact hostnames ("example.com"), one leading "_." wildcard that matches
+ subdomains at any depth but not the domain itself ("_.example.com" matches
+ api.example.com, not example.com), public IPv4 addresses ("8.8.8.8"), bracketed
+ public IPv6 addresses ("[2001:4860:4860::8888]"), or public CIDR ranges in
+ canonical form ("8.8.4.0/24", "2001:4860::/32"). IP and CIDR entries only match
+ destinations written as an IP address, never hostnames that resolve into the
+ range. Entries cannot include ports, paths, or schemes, and match every port on
+ their host. Wildcards over a public suffix ("_.com", "_.github.io") and private
+ or reserved IP ranges are rejected, as are entries that overlap private_hosts.
+ Enforced at Kernel's egress proxy only: destinations in private_hosts, and
+ processes in the browser VM that do not use the browser's proxy, are not
+ filtered, and Kernel's own control traffic is always allowed. Can be replaced or
+ removed while the session runs with PATCH /browsers/{id_or_name}, but not added
+ to a browser created without one. Supported on browser pools and their leased
+ browsers. Per-lease changes are reset to the pool's allowlist on release. If a
+ lessee removes the allowlist and the pool still requires one, the browser is
+ 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
+ instead of through Kernel-managed egress — for private hosts reachable over a
+ VPN or tunnel the session has joined (e.g. a Tailscale tailnet). By default,
+ private IP ranges already route directly: RFC1918 (10.0.0.0/8, 172.16.0.0/12,
+ 192.168.0.0/16), CGNAT/Tailscale (100.64.0.0/10), and IPv6 ULA (fc00::/7). An
+ explicitly supplied list replaces those defaults with exactly the entries given,
+ and an empty list ([]) disables them so all traffic uses Kernel-managed egress;
+ omit private_hosts to keep the defaults. Entries are hostname patterns
+ ("_.example.ts.net", "preview.internal") or IP/CIDR literals ("100.64.0.0/10",
+ "10.1.30.63"). IP and CIDR entries only match URLs written with a literal IP
+ address; they never match hostnames that resolve into the range, so private DNS
+ names need a hostname entry even when they resolve inside the default ranges.
+ CIDRs must be in canonical masked form (host bits zero), and only the private
+ ranges listed above are accepted; public, loopback, link-local, and unspecified
+ ranges are rejected. Exact IPv6 addresses must be bracketed ("[fd00::1]"); IPv6
+ CIDR ranges are unbracketed ("fd00::/8"). Wildcards are limited to one leading
+ "_." over a suffix with at least two labels that is not a public suffix (so
+ "_.co.uk" or "_.ts.net" are rejected, while "\\**.example.ts.net" is accepted).
+ Hostname and IP entries may carry a port; CIDR ranges may not. Hostname entries
+ are not resolved during validation, so callers must ensure they identify private
+ destinations. Not related to a proxy's bypass_hosts, which selects between
+ upstream-proxy and Kernel-managed direct egress and cannot reach into a VPN.
+ """
+
+ proxy_routes: Optional[List[ProxyRoute]] = None
+ """Per-destination proxy routes for a browser session.
+
+ A destination hostname is matched against every route's hosts, regardless of
+ port; route order does not matter. An exact hostname beats a wildcard, and a
+ longer wildcard suffix beats a shorter one (for a.b.example.com:
+ "a.b.example.com" > "_.b.example.com" > "_.example.com"). A host pattern may
+ appear in only one route. "\\**.example.com" matches subdomains only, not
+ example.com. A matched request selects the route's proxy instead of the
+ session's top-level proxy (including mode: direct); the route proxy's own
+ bypass_hosts still apply. If the route proxy becomes unavailable, matched
+ requests fail closed without falling back. Requests that match no route use the
+ session's default egress from the top-level proxy field (or the browser default
+ when proxy is omitted: stealth proxy or direct egress). Routes apply from the
+ start of the session, including to start_url and other traffic during browser
+ setup. Setting routes requires proxy v3. Browser pools also support these
+ routes.
+ """
diff --git a/src/kernel/types/browser_network_config_param.py b/src/kernel/types/browser_network_config_param.py
new file mode 100644
index 00000000..723fd6ee
--- /dev/null
+++ b/src/kernel/types/browser_network_config_param.py
@@ -0,0 +1,107 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["BrowserNetworkConfigParam", "ProxyRoute", "ProxyRouteProxy"]
+
+
+class ProxyRouteProxy(TypedDict, total=False):
+ """Select an active non-direct proxy by ID or name. Responses always use ID."""
+
+ id: str
+
+ name: str
+
+
+class ProxyRoute(TypedDict, total=False):
+ hosts: Required[SequenceNotStr[str]]
+ """Exact hostnames or leading \\**.
+
+ wildcard patterns (subdomains only); patterns cannot include ports, and matching
+ ignores the destination port. Hosts not matched by any route use the session's
+ top-level proxy (or the browser default when proxy is omitted).
+ """
+
+ proxy: Required[ProxyRouteProxy]
+ """Select an active non-direct proxy by ID or name. Responses always use ID."""
+
+
+class BrowserNetworkConfigParam(TypedDict, total=False):
+ """Network configuration for a browser session or browser pool."""
+
+ allowed_hosts: SequenceNotStr[str]
+ """
+ Egress allowlist for a browser session: the only destinations the browser may
+ reach through Kernel-managed egress. Any other destination is refused with a 403
+ whose X-Kernel-Proxy-Error header is network_policy_denied, so pages cannot load
+ or send data to unlisted hosts, including with fetch() and WebSockets. Omit the
+ field for unfiltered egress; an empty list is invalid. The allowlist applies
+ from the browser's first request, and start_url must be allowed by it. Entries
+ are exact hostnames ("example.com"), one leading "_." wildcard that matches
+ subdomains at any depth but not the domain itself ("_.example.com" matches
+ api.example.com, not example.com), public IPv4 addresses ("8.8.8.8"), bracketed
+ public IPv6 addresses ("[2001:4860:4860::8888]"), or public CIDR ranges in
+ canonical form ("8.8.4.0/24", "2001:4860::/32"). IP and CIDR entries only match
+ destinations written as an IP address, never hostnames that resolve into the
+ range. Entries cannot include ports, paths, or schemes, and match every port on
+ their host. Wildcards over a public suffix ("_.com", "_.github.io") and private
+ or reserved IP ranges are rejected, as are entries that overlap private_hosts.
+ Enforced at Kernel's egress proxy only: destinations in private_hosts, and
+ processes in the browser VM that do not use the browser's proxy, are not
+ filtered, and Kernel's own control traffic is always allowed. Can be replaced or
+ removed while the session runs with PATCH /browsers/{id_or_name}, but not added
+ to a browser created without one. Supported on browser pools and their leased
+ browsers. Per-lease changes are reset to the pool's allowlist on release. If a
+ lessee removes the allowlist and the pool still requires one, the browser is
+ replaced on release rather than given an allowlist again. Requires proxy v3.
+ """
+
+ private_hosts: SequenceNotStr[str]
+ """
+ Destinations the browser reaches directly through the session's own network
+ instead of through Kernel-managed egress — for private hosts reachable over a
+ VPN or tunnel the session has joined (e.g. a Tailscale tailnet). By default,
+ private IP ranges already route directly: RFC1918 (10.0.0.0/8, 172.16.0.0/12,
+ 192.168.0.0/16), CGNAT/Tailscale (100.64.0.0/10), and IPv6 ULA (fc00::/7). An
+ explicitly supplied list replaces those defaults with exactly the entries given,
+ and an empty list ([]) disables them so all traffic uses Kernel-managed egress;
+ omit private_hosts to keep the defaults. Entries are hostname patterns
+ ("_.example.ts.net", "preview.internal") or IP/CIDR literals ("100.64.0.0/10",
+ "10.1.30.63"). IP and CIDR entries only match URLs written with a literal IP
+ address; they never match hostnames that resolve into the range, so private DNS
+ names need a hostname entry even when they resolve inside the default ranges.
+ CIDRs must be in canonical masked form (host bits zero), and only the private
+ ranges listed above are accepted; public, loopback, link-local, and unspecified
+ ranges are rejected. Exact IPv6 addresses must be bracketed ("[fd00::1]"); IPv6
+ CIDR ranges are unbracketed ("fd00::/8"). Wildcards are limited to one leading
+ "_." over a suffix with at least two labels that is not a public suffix (so
+ "_.co.uk" or "_.ts.net" are rejected, while "\\**.example.ts.net" is accepted).
+ Hostname and IP entries may carry a port; CIDR ranges may not. Hostname entries
+ are not resolved during validation, so callers must ensure they identify private
+ destinations. Not related to a proxy's bypass_hosts, which selects between
+ upstream-proxy and Kernel-managed direct egress and cannot reach into a VPN.
+ """
+
+ proxy_routes: Iterable[ProxyRoute]
+ """Per-destination proxy routes for a browser session.
+
+ A destination hostname is matched against every route's hosts, regardless of
+ port; route order does not matter. An exact hostname beats a wildcard, and a
+ longer wildcard suffix beats a shorter one (for a.b.example.com:
+ "a.b.example.com" > "_.b.example.com" > "_.example.com"). A host pattern may
+ appear in only one route. "\\**.example.com" matches subdomains only, not
+ example.com. A matched request selects the route's proxy instead of the
+ session's top-level proxy (including mode: direct); the route proxy's own
+ bypass_hosts still apply. If the route proxy becomes unavailable, matched
+ requests fail closed without falling back. Requests that match no route use the
+ session's default egress from the top-level proxy field (or the browser default
+ when proxy is omitted: stealth proxy or direct egress). Routes apply from the
+ start of the session, including to start_url and other traffic during browser
+ setup. Setting routes requires proxy v3. Browser pools also support these
+ routes.
+ """
diff --git a/src/kernel/types/browser_network_update_param.py b/src/kernel/types/browser_network_update_param.py
new file mode 100644
index 00000000..f329552d
--- /dev/null
+++ b/src/kernel/types/browser_network_update_param.py
@@ -0,0 +1,55 @@
+# 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
+
+from .._types import SequenceNotStr
+
+__all__ = ["BrowserNetworkUpdateParam"]
+
+
+class BrowserNetworkUpdateParam(TypedDict, total=False):
+ """Network configuration changes for a running browser session.
+
+ Other network settings can only be set at creation.
+ """
+
+ allowed_hosts: Optional[SequenceNotStr[str]]
+ """
+ Replaces the session's egress allowlist, using the same entry rules as
+ network.allowed_hosts on create. Only an allowlist the browser was created with
+ can be changed: a browser created without one can't be given one, and an
+ allowlist removed with null can't be added back. Omit to leave the allowlist
+ unchanged, or set to null to remove it and return to unfiltered egress; an empty
+ list is invalid. The new list applies without restarting the browser: new
+ requests to destinations it no longer allows are refused within a few seconds,
+ and open connections to them are closed within about 30 seconds, or up to 10
+ minutes during a Kernel deploy. Connections to destinations it still allows,
+ such as WebSockets, stay open. A start_url in the same request must be allowed
+ by the updated list, and is loaded only after the list takes effect. Requires a
+ browser created with proxy v3. Supported on leased pooled browsers; the pool's
+ allowlist is restored before reuse, or the browser is destroyed if it cannot be
+ 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/src/kernel/types/browser_persistence.py b/src/kernel/types/browser_persistence.py
deleted file mode 100644
index 381d6306..00000000
--- a/src/kernel/types/browser_persistence.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from .._models import BaseModel
-
-__all__ = ["BrowserPersistence"]
-
-
-class BrowserPersistence(BaseModel):
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
-
- id: str
- """DEPRECATED: Unique identifier for the persistent browser session."""
diff --git a/src/kernel/types/browser_persistence_param.py b/src/kernel/types/browser_persistence_param.py
deleted file mode 100644
index 6109abfd..00000000
--- a/src/kernel/types/browser_persistence_param.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Required, TypedDict
-
-__all__ = ["BrowserPersistenceParam"]
-
-
-class BrowserPersistenceParam(TypedDict, total=False):
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
-
- id: Required[str]
- """DEPRECATED: Unique identifier for the persistent browser session."""
diff --git a/src/kernel/types/browser_pool.py b/src/kernel/types/browser_pool.py
index a691d0eb..035b5722 100644
--- a/src/kernel/types/browser_pool.py
+++ b/src/kernel/types/browser_pool.py
@@ -2,20 +2,42 @@
from typing import Dict, List, Optional
from datetime import datetime
+from typing_extensions import Literal
from .._models import BaseModel
-from .shared.browser_profile import BrowserProfile
+from .browser_memory import BrowserMemory
+from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
from .shared.browser_extension import BrowserExtension
+from .browsers.browser_telemetry_config import BrowserTelemetryConfig
-__all__ = ["BrowserPool", "BrowserPoolConfig"]
+__all__ = ["BrowserPool", "BrowserPoolConfig", "BrowserPoolConfigProfile"]
+
+
+class BrowserPoolConfigProfile(BaseModel):
+ """Profile configuration for browsers in a pool.
+
+ Provide either id or name. Profiles must
+ be created beforehand. Unlike single browser sessions, pools load the profile read-only
+ and never persist changes back to it, so save_changes is omitted here. Any save_changes
+ value sent on a pool profile is silently ignored rather than rejected.
+ """
+
+ id: Optional[str] = None
+ """Profile ID to load for browsers in this pool"""
+
+ name: Optional[str] = None
+ """Profile name to load for browsers in this pool (instead of id).
+
+ Must be 1-255 characters, using letters, numbers, dots, underscores, or hyphens.
+ """
class BrowserPoolConfig(BaseModel):
"""Configuration used to create all browsers in this pool"""
size: int
- """Number of browsers to maintain in the pool.
+ """Number of browsers maintained in the pool.
The maximum size is determined by your organization's pooled sessions limit (the
sum of all pool sizes cannot exceed your limit).
@@ -26,7 +48,8 @@ class BrowserPoolConfig(BaseModel):
Keys are Chrome enterprise policy names; values must match their expected types.
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
- https://chromeenterprise.google/policies/
+ https://chromeenterprise.google/policies/ The serialized JSON payload is capped
+ at 5 MiB.
"""
extensions: Optional[List[BrowserExtension]] = None
@@ -36,10 +59,14 @@ class BrowserPoolConfig(BaseModel):
"""
fill_rate_per_minute: Optional[int] = None
- """Percentage of the pool to fill per minute. Defaults to 10%."""
+ """Percentage of the pool to fill per minute.
+
+ The cap is 25 for most organizations but can be raised per-organization, so only
+ the lower bound is enforced here.
+ """
headless: Optional[bool] = None
- """If true, launches the browser using a headless image. Defaults to false."""
+ """If true, launches the browser using a headless image."""
kiosk_mode: Optional[bool] = None
"""
@@ -47,28 +74,46 @@ class BrowserPoolConfig(BaseModel):
view.
"""
+ memory: Optional[BrowserMemory] = None
+ """Memory allocated to the browser session."""
+
name: Optional[str] = None
"""Optional name for the browser pool. Must be unique within the project."""
- profile: Optional[BrowserProfile] = None
- """Profile selection for the browser session.
+ network: Optional[BrowserNetworkConfig] = None
+ """Network configuration applied to browsers in this pool, if any.
+
+ Omitted when the pool has no network configuration.
+ """
+
+ profile: Optional[BrowserPoolConfigProfile] = None
+ """Profile configuration for browsers in a pool.
- Provide either id or name. If specified, the matching profile will be loaded
- into the browser session. Profiles must be created beforehand.
+ Provide either id or name. Profiles must be created beforehand. Unlike single
+ browser sessions, pools load the profile read-only and never persist changes
+ back to it, so save_changes is omitted here. Any save_changes value sent on a
+ pool profile is silently ignored rather than rejected.
"""
proxy_id: Optional[str] = None
- """Optional proxy to associate to the browser session.
+ """Optional proxy associated to the browser session.
+
+ References a proxy in the same project as the browser session.
+ """
- Must reference a proxy belonging to the caller's org.
+ refresh_on_profile_update: Optional[bool] = None
+ """
+ When true, flush idle browsers when the profile the pool uses is updated, so
+ pool browsers pick up the latest profile data. When a profile is provided during
+ creation, this defaults to true. Requires a profile to be set on the pool.
"""
start_url: Optional[str] = None
"""Optional URL to navigate to when a new browser is warmed into the pool.
Best-effort: failures to navigate do not fail pool fill. Only applied to
- newly-warmed browsers — browsers reused via release/acquire keep whatever URL
- the previous lease left them on. Accepts any URL Chromium can resolve, including
+ newly-warmed browsers; browsers reused via release/acquire keep whatever URL the
+ previous lease left them on. Accepts any URL Chromium can resolve, including
chrome:// pages.
"""
@@ -78,10 +123,16 @@ class BrowserPoolConfig(BaseModel):
mechanisms.
"""
+ telemetry: Optional[BrowserTelemetryConfig] = None
+ """
+ Active telemetry configuration applied to browsers warmed into this pool, if
+ any.
+ """
+
timeout_seconds: Optional[int] = None
"""
Default idle timeout in seconds for browsers acquired from this pool before they
- are destroyed. Defaults to 600 seconds if not specified
+ are destroyed. Minimum 10, maximum 259200 (72 hours).
"""
viewport: Optional[BrowserViewport] = None
@@ -90,14 +141,14 @@ class BrowserPoolConfig(BaseModel):
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
@@ -119,5 +170,24 @@ class BrowserPool(BaseModel):
created_at: datetime
"""Timestamp when the browser pool was created"""
+ extension_ids: List[str]
+ """Resolved extension IDs attached to the pool, in configured load order.
+
+ Empty when no extensions are attached. Authoritative for programmatic consumers;
+ the extensions inside `browser_pool_config` reflect the configured selector
+ (echoed as sent on create).
+ """
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region of the browser pool. Fixed once the pool is created."""
+
name: Optional[str] = None
"""Browser pool name, if set"""
+
+ profile_id: Optional[str] = None
+ """Resolved profile ID the pool is attached to.
+
+ Omitted when no profile is attached. Authoritative for programmatic consumers;
+ the profile inside `browser_pool_config` reflects the configured selector
+ (echoed as sent on create).
+ """
diff --git a/src/kernel/types/browser_pool_acquire_params.py b/src/kernel/types/browser_pool_acquire_params.py
index d0df921a..0a74c176 100644
--- a/src/kernel/types/browser_pool_acquire_params.py
+++ b/src/kernel/types/browser_pool_acquire_params.py
@@ -2,9 +2,21 @@
from __future__ import annotations
+from typing import Optional
from typing_extensions import TypedDict
-__all__ = ["BrowserPoolAcquireParams"]
+from .tags_param import TagsParam
+from .shared_params.browser_profile import BrowserProfile
+from .browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam
+
+__all__ = [
+ "BrowserPoolAcquireParams",
+ "Telemetry",
+ "TelemetryExport",
+ "TelemetryExportOtlp",
+ "TelemetryExportOtlpDestination",
+ "TelemetryStorage",
+]
class BrowserPoolAcquireParams(TypedDict, total=False):
@@ -14,3 +26,142 @@ class BrowserPoolAcquireParams(TypedDict, total=False):
Defaults to the calculated time it would take to fill the pool at the currently
configured fill rate.
"""
+
+ name: str
+ """
+ Optional human-readable name for the acquired browser session, used to find it
+ later in the dashboard. Must be unique among active sessions within the pool's
+ project. Applies to this lease only and is cleared when the browser is released
+ back to the pool.
+ """
+
+ profile: BrowserProfile
+ """Profile selection for the browser session.
+
+ Provide either id or name. If specified, the matching profile will be loaded
+ into the browser session. Profiles must be created beforehand.
+ """
+
+ start_url: str
+ """Optional URL to navigate the acquired browser to.
+
+ Overrides the pool's start_url for this acquire only. Best-effort: failures to
+ navigate do not fail the acquire.
+ """
+
+ tags: TagsParam
+ """
+ Optional user-defined key-value tags for the acquired browser session, used to
+ find and group sessions later. Applies to this lease only and are cleared when
+ the browser is released back to the pool. Up to 50 pairs.
+ """
+
+ telemetry: Optional[Telemetry]
+ """Telemetry override for the acquired browser, applied to this lease only.
+
+ Merges onto the browser's current (pool-inherited) telemetry using the same
+ per-category semantics as PATCH /browsers: provided categories override the
+ current configuration, omitted categories are inherited. Set enabled to true to
+ resolve the config fresh from the default set, or enabled to false to stop
+ capture. When the browser is released back to the pool with reuse, its telemetry
+ is reset to the pool's baseline, so the override does not carry over to the next
+ lease.
+ """
+
+
+class TelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class TelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: TelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class TelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: TelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class TelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class Telemetry(TypedDict, total=False):
+ """Telemetry override for the acquired browser, applied to this lease only.
+
+ Merges onto the browser's current (pool-inherited) telemetry using the same per-category semantics as PATCH /browsers: provided categories override the current configuration, omitted categories are inherited. Set enabled to true to resolve the config fresh from the default set, or enabled to false to stop capture. When the browser is released back to the pool with reuse, its telemetry is reset to the pool's baseline, so the override does not carry over to the next lease.
+ """
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: TelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: TelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
diff --git a/src/kernel/types/browser_pool_acquire_response.py b/src/kernel/types/browser_pool_acquire_response.py
index cff8286d..cceb9892 100644
--- a/src/kernel/types/browser_pool_acquire_response.py
+++ b/src/kernel/types/browser_pool_acquire_response.py
@@ -1,19 +1,28 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, List, Optional
from datetime import datetime
+from typing_extensions import Literal
+from .tags import Tags
+from .._types import NoContentAsNone
from .profile import Profile
from .._models import BaseModel
+from .browser_proxy import BrowserProxy
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_persistence import BrowserPersistence
+from .browser_video_memory import BrowserVideoMemory
+from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
+from .browsers.browser_telemetry_config import BrowserTelemetryConfig
__all__ = ["BrowserPoolAcquireResponse"]
-class BrowserPoolAcquireResponse(BaseModel):
+class BrowserPoolAcquireResponse(NoContentAsNone, BaseModel):
cdp_ws_url: str
"""Websocket URL for Chrome DevTools Protocol connections to the browser session"""
@@ -23,6 +32,12 @@ class BrowserPoolAcquireResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region of the browser session. Fixed once the session is created."""
+
session_id: str
"""Unique identifier for the browser session"""
@@ -44,6 +59,13 @@ class BrowserPoolAcquireResponse(BaseModel):
Only available for non-headless browsers.
"""
+ chrome_policy: Optional[Dict[str, object]] = None
+ """
+ Custom Chrome enterprise policy overrides that were applied to this browser
+ session, if any. Echoed back for verification. Keys are Chrome enterprise policy
+ names.
+ """
+
deleted_at: Optional[datetime] = None
"""When the browser session was soft-deleted. Only present for deleted sessions."""
@@ -56,8 +78,17 @@ class BrowserPoolAcquireResponse(BaseModel):
kiosk_mode: Optional[bool] = None
"""Whether the browser session is running in kiosk mode."""
- persistence: Optional[BrowserPersistence] = None
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+ 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."""
+
+ network: Optional[BrowserNetworkConfig] = None
+ """Network configuration the session was created with, if any.
+
+ Omitted when the session has no network configuration.
+ """
pool: Optional[BrowserPoolRef] = None
"""Browser pool this session was acquired from, if any."""
@@ -65,30 +96,66 @@ class BrowserPoolAcquireResponse(BaseModel):
profile: Optional[Profile] = None
"""Browser profile metadata."""
+ profile_save_changes: Optional[bool] = None
+ """
+ Whether changes made during this browser session are saved back to its profile
+ when the session ends. Omitted when no profile is attached.
+ """
+
+ proxy: Optional[BrowserProxy] = None
+ """Resolved proxy configuration for this browser session."""
+
proxy_id: Optional[str] = None
- """ID of the proxy associated with this browser session, if any."""
+ """ID of the proxy associated with this browser session, if any.
+
+ Deprecated in favor of proxy.
+ """
start_url: Optional[str] = None
- """URL the session was asked to navigate to on creation, if any.
+ """URL the session was most recently asked to navigate to, if any.
+
+ Recorded for debugging. Navigation is fire-and-forget — the URL is dispatched to
+ the browser without waiting for it to load, and any errors (DNS failure, bad
+ status, timeout) are silently dropped. Captures what was requested, not what the
+ browser actually loaded.
+ """
+
+ tags: Optional[Tags] = None
+ """User-defined key-value tags that were set on this browser session, if any.
- Recorded for debugging — navigation is best-effort and may have failed.
+ Echoed back when present.
"""
+ telemetry: Optional[BrowserTelemetryConfig] = None
+ """Active telemetry configuration for the session, if any."""
+
usage: Optional[BrowserUsage] = None
"""Session usage metrics."""
+ usage_status: Optional[Literal["pending", "ready"]] = None
+ """Whether final usage billing is still pending or complete.
+
+ Only present for deleted sessions.
+ """
+
+ vaults: Optional[List[VaultReference]] = None
+ """Vaults linked when the browser session was created."""
+
+ video_memory: Optional[BrowserVideoMemory] = None
+ """Video memory (VRAM) of the GPU browser session. Only present when gpu is true."""
+
viewport: Optional[BrowserViewport] = None
"""
Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
diff --git a/src/kernel/types/browser_pool_create_params.py b/src/kernel/types/browser_pool_create_params.py
index 99d6c9cd..bf05dd92 100644
--- a/src/kernel/types/browser_pool_create_params.py
+++ b/src/kernel/types/browser_pool_create_params.py
@@ -2,14 +2,24 @@
from __future__ import annotations
-from typing import Dict, Iterable
-from typing_extensions import Required, TypedDict
+from typing import Dict, Iterable, Optional
+from typing_extensions import Literal, Required, TypedDict
-from .shared_params.browser_profile import BrowserProfile
+from .browser_memory_request import BrowserMemoryRequest
+from .browser_network_config_param import BrowserNetworkConfigParam
from .shared_params.browser_viewport import BrowserViewport
from .shared_params.browser_extension import BrowserExtension
+from .browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam
-__all__ = ["BrowserPoolCreateParams"]
+__all__ = [
+ "BrowserPoolCreateParams",
+ "Profile",
+ "Telemetry",
+ "TelemetryExport",
+ "TelemetryExportOtlp",
+ "TelemetryExportOtlpDestination",
+ "TelemetryStorage",
+]
class BrowserPoolCreateParams(TypedDict, total=False):
@@ -25,7 +35,8 @@ class BrowserPoolCreateParams(TypedDict, total=False):
Keys are Chrome enterprise policy names; values must match their expected types.
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
- https://chromeenterprise.google/policies/
+ https://chromeenterprise.google/policies/ The serialized JSON payload is capped
+ at 5 MiB.
"""
extensions: Iterable[BrowserExtension]
@@ -35,7 +46,11 @@ class BrowserPoolCreateParams(TypedDict, total=False):
"""
fill_rate_per_minute: int
- """Percentage of the pool to fill per minute. Defaults to 10%."""
+ """Percentage of the pool to fill per minute.
+
+ Defaults to 25. The cap is 25 for most organizations but can be raised
+ per-organization, so only the lower bound is enforced here.
+ """
headless: bool
"""If true, launches the browser using a headless image. Defaults to false."""
@@ -43,44 +58,83 @@ class BrowserPoolCreateParams(TypedDict, total=False):
kiosk_mode: bool
"""
If true, launches the browser in kiosk mode to hide address bar and tabs in live
- view.
+ view. Defaults to false.
+ """
+
+ memory: BrowserMemoryRequest
+ """Memory for browsers in this pool.
+
+ Headless: 1GiB (default), 2GiB, or 4GiB. Headful: 8GiB (default) or 16GiB.
"""
name: str
"""Optional name for the browser pool. Must be unique within the project."""
- profile: BrowserProfile
- """Profile selection for the browser session.
+ network: BrowserNetworkConfigParam
+ """Network configuration applied to browsers in this pool.
- Provide either id or name. If specified, the matching profile will be loaded
- into the browser session. Profiles must be created beforehand.
+ Egress allowlists and proxy routes require proxy-v3; the pool will not provision
+ browsers through Envoy if proxy-v3 is unavailable.
+ """
+
+ profile: Profile
+ """Profile configuration for browsers in a pool.
+
+ Provide either id or name. Profiles must be created beforehand. Unlike single
+ browser sessions, pools load the profile read-only and never persist changes
+ back to it, so save_changes is omitted here. Any save_changes value sent on a
+ pool profile is silently ignored rather than rejected.
"""
proxy_id: str
"""Optional proxy to associate to the browser session.
- Must reference a proxy belonging to the caller's org.
+ Must reference a proxy in the same project as the browser session.
+ """
+
+ refresh_on_profile_update: bool
+ """
+ When true, flush idle browsers when the profile the pool uses is updated, so
+ pool browsers pick up the latest profile data. When a profile is provided during
+ creation, this defaults to true. Requires a profile to be set on the pool.
+ """
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region for the browser pool.
+
+ It is fixed once the pool is created. Region selection requires a Start-Up or
+ Enterprise plan, defaults to us-east when omitted on create.
"""
start_url: str
"""Optional URL to navigate to when a new browser is warmed into the pool.
Best-effort: failures to navigate do not fail pool fill. Only applied to
- newly-warmed browsers — browsers reused via release/acquire keep whatever URL
- the previous lease left them on. Accepts any URL Chromium can resolve, including
+ newly-warmed browsers; browsers reused via release/acquire keep whatever URL the
+ previous lease left them on. Accepts any URL Chromium can resolve, including
chrome:// pages.
"""
stealth: bool
"""
If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
+ mechanisms. Defaults to false.
+ """
+
+ telemetry: Optional[Telemetry]
+ """Telemetry configuration applied to browsers warmed into this pool.
+
+ Set enabled to true to start capture using the default set, or provide browser
+ category settings. If omitted, null, set to an empty object ({}), set to
+ enabled: false without browser category settings, or all four CDP categories are
+ explicitly disabled, no telemetry is configured on the pool. Only applied to
+ newly-warmed browsers.
"""
timeout_seconds: int
"""
Default idle timeout in seconds for browsers acquired from this pool before they
- are destroyed. Defaults to 600 seconds if not specified
+ are destroyed. Defaults to 600 seconds. Minimum 10, maximum 259200 (72 hours).
"""
viewport: BrowserViewport
@@ -89,12 +143,129 @@ class BrowserPoolCreateParams(TypedDict, total=False):
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
+
+
+class Profile(TypedDict, total=False):
+ """Profile configuration for browsers in a pool.
+
+ Provide either id or name. Profiles must
+ be created beforehand. Unlike single browser sessions, pools load the profile read-only
+ and never persist changes back to it, so save_changes is omitted here. Any save_changes
+ value sent on a pool profile is silently ignored rather than rejected.
+ """
+
+ id: str
+ """Profile ID to load for browsers in this pool"""
+
+ name: str
+ """Profile name to load for browsers in this pool (instead of id).
+
+ Must be 1-255 characters, using letters, numbers, dots, underscores, or hyphens.
+ """
+
+
+class TelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class TelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: TelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class TelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: TelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class TelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class Telemetry(TypedDict, total=False):
+ """Telemetry configuration applied to browsers warmed into this pool.
+
+ Set enabled to true to start capture using the default set, or provide browser category settings. If omitted, null, set to an empty object ({}), set to enabled: false without browser category settings, or all four CDP categories are explicitly disabled, no telemetry is configured on the pool. Only applied to newly-warmed browsers.
+ """
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: TelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: TelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
diff --git a/src/kernel/types/browser_pool_list_params.py b/src/kernel/types/browser_pool_list_params.py
new file mode 100644
index 00000000..8f9961f4
--- /dev/null
+++ b/src/kernel/types/browser_pool_list_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["BrowserPoolListParams"]
+
+
+class BrowserPoolListParams(TypedDict, total=False):
+ limit: int
+ """Limit the number of browser pools to return."""
+
+ name: str
+ """Exact-match filter on browser pool name using the database collation.
+
+ In production, matching is case- and accent-insensitive. During the
+ default-project migration, unscoped requests prefer a concrete default-project
+ browser pool over a legacy unscoped browser pool with the same name.
+ """
+
+ offset: int
+ """Offset the number of browser pools to return."""
+
+ query: str
+ """Case-insensitive substring match against browser pool name.
+
+ IDs match by exact value.
+ """
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Filter pools by geographic region. Omit to list pools in all regions."""
diff --git a/src/kernel/types/browser_pool_release_params.py b/src/kernel/types/browser_pool_release_params.py
index 104b0b0c..5d5e6655 100644
--- a/src/kernel/types/browser_pool_release_params.py
+++ b/src/kernel/types/browser_pool_release_params.py
@@ -14,5 +14,9 @@ class BrowserPoolReleaseParams(TypedDict, total=False):
reuse: bool
"""Whether to reuse the browser instance or destroy it and create a new one.
- Defaults to true.
+ Defaults to true. A reused browser keeps the configuration it was created with,
+ so it does not pick up pool configuration changes made while it was in use.
+ Release with `reuse: false`, or flush the pool afterward, to rebuild it with the
+ current configuration. Browsers loaded with an acquire-time profile are always
+ destroyed and replaced, even when reuse is true.
"""
diff --git a/src/kernel/types/browser_pool_update_params.py b/src/kernel/types/browser_pool_update_params.py
index 5b069c4c..4b5c6a0f 100644
--- a/src/kernel/types/browser_pool_update_params.py
+++ b/src/kernel/types/browser_pool_update_params.py
@@ -2,91 +2,164 @@
from __future__ import annotations
-from typing import Dict, Iterable
-from typing_extensions import Required, TypedDict
+from typing import Dict, Iterable, Optional
+from typing_extensions import Literal, TypedDict
-from .shared_params.browser_profile import BrowserProfile
+from .browser_network_config_param import BrowserNetworkConfigParam
from .shared_params.browser_viewport import BrowserViewport
from .shared_params.browser_extension import BrowserExtension
+from .browsers.browser_telemetry_categories_config_param import BrowserTelemetryCategoriesConfigParam
-__all__ = ["BrowserPoolUpdateParams"]
+__all__ = [
+ "BrowserPoolUpdateParams",
+ "Profile",
+ "Telemetry",
+ "TelemetryExport",
+ "TelemetryExportOtlp",
+ "TelemetryExportOtlpDestination",
+ "TelemetryStorage",
+]
class BrowserPoolUpdateParams(TypedDict, total=False):
- size: Required[int]
- """Number of browsers to maintain in the pool.
-
- The maximum size is determined by your organization's pooled sessions limit (the
- sum of all pool sizes cannot exceed your limit).
- """
-
chrome_policy: Dict[str, object]
- """Custom Chrome enterprise policy overrides applied to all browsers in this pool.
-
- Keys are Chrome enterprise policy names; values must match their expected types.
+ """
+ If provided, replaces the custom Chrome enterprise policy overrides applied to
+ all browsers in this pool. Empty object clears any previously-set policy. Keys
+ are Chrome enterprise policy names; values must match their expected types.
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
- https://chromeenterprise.google/policies/
+ https://chromeenterprise.google/policies/ The serialized JSON payload is capped
+ at 5 MiB.
"""
discard_all_idle: bool
- """Whether to discard all idle browsers and rebuild the pool immediately.
+ """
+ Whether to discard all idle browsers and rebuild them immediately with the new
+ configuration. Defaults to false. Only browsers that are idle when the update
+ runs are rebuilt. A browser that is in use during the update keeps its original
+ configuration, except that its allowlist is reset to the current pool baseline
+ on release or the browser is replaced. For other configuration fields, if it is
+ later released with `reuse: true` it returns to the pool with that stale
+ configuration until it is discarded (by this flag on a later update, or by
+ flushing the pool).
+ """
- Defaults to false.
+ expected_network: BrowserNetworkConfigParam
+ """Optional optimistic concurrency precondition.
+
+ Send the network configuration from the last pool response, or {} when it has no
+ network configuration. The entire update is rejected with 409 stale_network if
+ the saved network differs, checked atomically with the write. Omit for
+ unconditional updates. Explicit empty lists differ from omitted lists; route
+ targets must match the saved references (resolved IDs, not equivalent proxy
+ names).
"""
extensions: Iterable[BrowserExtension]
- """List of browser extensions to load into the session.
+ """If provided, replaces the extension list.
- Provide each by id or name.
+ Empty array clears all previously-selected extensions. Omit this field to leave
+ extensions unchanged.
"""
fill_rate_per_minute: int
- """Percentage of the pool to fill per minute. Defaults to 10%."""
+ """If provided, replaces the percentage of the pool to fill per minute.
+
+ The cap is 25 for most organizations but can be raised per-organization, so only
+ the lower bound is enforced here.
+ """
headless: bool
- """If true, launches the browser using a headless image. Defaults to false."""
+ """If provided, replaces whether browsers launch using a headless image."""
kiosk_mode: bool
+ """If provided, replaces whether browsers launch in kiosk mode."""
+
+ memory: Literal["1GiB", "2GiB", "4GiB", "8GiB", "16GiB"]
"""
- If true, launches the browser in kiosk mode to hide address bar and tabs in live
- view.
+ Memory requested for newly-warmed browsers in this pool: 1GiB, 2GiB, or 4GiB for
+ headless, 8GiB or 16GiB for headful. Existing browsers retain their original
+ allocation. Use discard_all_idle to replace idle browsers.
"""
name: str
- """Optional name for the browser pool. Must be unique within the project."""
+ """If provided, replaces the pool name.
+
+ Empty string is a no-op; the pool name cannot be cleared or reset to empty once
+ assigned.
+ """
+
+ network: BrowserNetworkConfigParam
+ """If provided, replaces the pool's network configuration.
- profile: BrowserProfile
- """Profile selection for the browser session.
+ Omit to leave the existing configuration unchanged; an empty object ({}) removes
+ it, while network: {private_hosts: []} or network: {proxy_routes: []} sets an
+ explicit empty list; allowed_hosts must be non-empty when supplied. Egress
+ allowlists and proxy routes require proxy-v3; the pool will not provision
+ browsers through Envoy if proxy-v3 is unavailable. Changing allowed_hosts
+ automatically replaces idle browsers. Leased browsers retain their allowlist
+ until release, when it is reset to the current pool baseline or the browser is
+ replaced if it cannot safely receive that baseline. Stale browsers cannot be
+ acquired. Other network fields only apply to browsers created after the update;
+ existing browsers keep those fields until discarded (see discard_all_idle).
+ """
+
+ profile: Profile
+ """Profile configuration for browsers in a pool.
- Provide either id or name. If specified, the matching profile will be loaded
- into the browser session. Profiles must be created beforehand.
+ Provide either id or name. Profiles must be created beforehand. Unlike single
+ browser sessions, pools load the profile read-only and never persist changes
+ back to it, so save_changes is omitted here. Any save_changes value sent on a
+ pool profile is silently ignored rather than rejected.
"""
proxy_id: str
- """Optional proxy to associate to the browser session.
+ """Empty string clears the previously-selected proxy.
- Must reference a proxy belonging to the caller's org.
+ Omit this field to leave the proxy unchanged.
"""
- start_url: str
- """Optional URL to navigate to when a new browser is warmed into the pool.
+ refresh_on_profile_update: bool
+ """
+ If provided, replaces whether idle browsers are flushed when the profile the
+ pool uses is updated. When the pool's profile reference is changed (including
+ newly attached) and this field is omitted, it defaults to true. Re-sending the
+ same profile reference leaves this setting unchanged. Clearing the profile also
+ disables this setting. Requires a profile to be set on the pool.
+ """
- Best-effort: failures to navigate do not fail pool fill. Only applied to
- newly-warmed browsers — browsers reused via release/acquire keep whatever URL
- the previous lease left them on. Accepts any URL Chromium can resolve, including
- chrome:// pages.
+ size: int
+ """If provided, replaces the number of browsers to maintain in the pool.
+
+ The maximum size is determined by your organization's pooled sessions limit (the
+ sum of all pool sizes cannot exceed your limit).
"""
- stealth: bool
+ start_url: str
"""
- If true, launches the browser in stealth mode to reduce detection by anti-bot
- mechanisms.
+ If provided, replaces the URL to navigate to when a new browser is warmed into
+ the pool. Empty string clears the previously-set URL. Omit this field to leave
+ it unchanged.
+ """
+
+ stealth: bool
+ """If provided, replaces whether browsers launch in stealth mode."""
+
+ telemetry: Optional[Telemetry]
+ """If provided, updates the pool's telemetry configuration.
+
+ Omit, set to null, or set to an empty object ({}) to leave the existing
+ configuration unchanged. Set enabled to true to enable capture using the default
+ set. Set enabled to false to clear the pool's telemetry. Provide browser
+ category settings for per-category updates, merged onto the pool's current
+ configuration. Only applied to browsers warmed after the update; browsers
+ already in the pool keep their configuration until discarded.
"""
timeout_seconds: int
"""
- Default idle timeout in seconds for browsers acquired from this pool before they
- are destroyed. Defaults to 600 seconds if not specified
+ If provided, replaces the default idle timeout in seconds for browsers acquired
+ from this pool before they are destroyed. Minimum 10, maximum 259200 (72 hours).
"""
viewport: BrowserViewport
@@ -95,12 +168,129 @@ class BrowserPoolUpdateParams(TypedDict, total=False):
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
+
+
+class Profile(TypedDict, total=False):
+ """Profile configuration for browsers in a pool.
+
+ Provide either id or name. Profiles must
+ be created beforehand. Unlike single browser sessions, pools load the profile read-only
+ and never persist changes back to it, so save_changes is omitted here. Any save_changes
+ value sent on a pool profile is silently ignored rather than rejected.
+ """
+
+ id: str
+ """Profile ID to load for browsers in this pool"""
+
+ name: str
+ """Profile name to load for browsers in this pool (instead of id).
+
+ Must be 1-255 characters, using letters, numbers, dots, underscores, or hyphens.
+ """
+
+
+class TelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class TelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: TelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class TelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: TelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class TelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class Telemetry(TypedDict, total=False):
+ """If provided, updates the pool's telemetry configuration.
+
+ Omit, set to null, or set to an empty object ({}) to leave the existing configuration unchanged. Set enabled to true to enable capture using the default set. Set enabled to false to clear the pool's telemetry. Provide browser category settings for per-category updates, merged onto the pool's current configuration. Only applied to browsers warmed after the update; browsers already in the pool keep their configuration until discarded.
+ """
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: TelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: TelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
diff --git a/src/kernel/types/browser_proxy.py b/src/kernel/types/browser_proxy.py
new file mode 100644
index 00000000..20078429
--- /dev/null
+++ b/src/kernel/types/browser_proxy.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .browser_proxy_mode import BrowserProxyMode
+
+__all__ = ["BrowserProxy"]
+
+
+class BrowserProxy(BaseModel):
+ """Resolved proxy configuration for a browser session.
+
+ Selected proxies are returned by stable ID.
+ """
+
+ id: Optional[str] = None
+ """Selected proxy ID."""
+
+ mode: Optional[BrowserProxyMode] = None
+ """Proxy egress mode.
+
+ direct forces no proxy regardless of stealth. default uses the browser's
+ stealth-derived default: Kernel's default stealth proxy when stealth=true, or
+ direct egress when stealth=false. default is primarily useful on browser update
+ to restore the browser default after selected-proxy egress.
+ """
+
+ name: Optional[str] = None
+ """Selected proxy name."""
diff --git a/src/kernel/types/browser_proxy_config.py b/src/kernel/types/browser_proxy_config.py
new file mode 100644
index 00000000..6db5d5ec
--- /dev/null
+++ b/src/kernel/types/browser_proxy_config.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .browser_proxy_mode import BrowserProxyMode
+
+__all__ = ["BrowserProxyConfig"]
+
+
+class BrowserProxyConfig(BaseModel):
+ """Browser proxy configuration.
+
+ Provide exactly one of mode, id, or name; an empty object is invalid.
+ Set mode to direct for no proxy regardless of stealth. Set mode to default to use the browser's stealth-derived default: Kernel's default stealth proxy when stealth=true, or direct egress when stealth=false.
+ Select id or name to use that proxy regardless of stealth. The selected proxy must be in the same project as the browser. Names must match exactly one active proxy; use id for stable references.
+ Proxy configuration changes only egress and does not change stealth or CAPTCHA solver behavior. A stealth browser using mode=direct still runs in stealth mode with the CAPTCHA solver enabled.
+ When proxy is omitted on browser creation, stealth browsers use Kernel's default stealth proxy and non-stealth browsers use direct egress. When omitted on update, the current configuration is unchanged.
+ """
+
+ id: Optional[str] = None
+ """Proxy ID."""
+
+ mode: Optional[BrowserProxyMode] = None
+ """Proxy egress mode.
+
+ direct forces no proxy regardless of stealth. default uses the browser's
+ stealth-derived default: Kernel's default stealth proxy when stealth=true, or
+ direct egress when stealth=false. default is primarily useful on browser update
+ to restore the browser default after selected-proxy egress.
+ """
+
+ name: Optional[str] = None
+ """Proxy name. Must match exactly one active proxy in the project."""
diff --git a/src/kernel/types/browser_proxy_config_param.py b/src/kernel/types/browser_proxy_config_param.py
new file mode 100644
index 00000000..0fc83157
--- /dev/null
+++ b/src/kernel/types/browser_proxy_config_param.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from .browser_proxy_mode import BrowserProxyMode
+
+__all__ = ["BrowserProxyConfigParam"]
+
+
+class BrowserProxyConfigParam(TypedDict, total=False):
+ """Browser proxy configuration.
+
+ Provide exactly one of mode, id, or name; an empty object is invalid.
+ Set mode to direct for no proxy regardless of stealth. Set mode to default to use the browser's stealth-derived default: Kernel's default stealth proxy when stealth=true, or direct egress when stealth=false.
+ Select id or name to use that proxy regardless of stealth. The selected proxy must be in the same project as the browser. Names must match exactly one active proxy; use id for stable references.
+ Proxy configuration changes only egress and does not change stealth or CAPTCHA solver behavior. A stealth browser using mode=direct still runs in stealth mode with the CAPTCHA solver enabled.
+ When proxy is omitted on browser creation, stealth browsers use Kernel's default stealth proxy and non-stealth browsers use direct egress. When omitted on update, the current configuration is unchanged.
+ """
+
+ id: str
+ """Proxy ID."""
+
+ mode: BrowserProxyMode
+ """Proxy egress mode.
+
+ direct forces no proxy regardless of stealth. default uses the browser's
+ stealth-derived default: Kernel's default stealth proxy when stealth=true, or
+ direct egress when stealth=false. default is primarily useful on browser update
+ to restore the browser default after selected-proxy egress.
+ """
+
+ name: str
+ """Proxy name. Must match exactly one active proxy in the project."""
diff --git a/src/kernel/types/browser_proxy_mode.py b/src/kernel/types/browser_proxy_mode.py
new file mode 100644
index 00000000..c8b51c1b
--- /dev/null
+++ b/src/kernel/types/browser_proxy_mode.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__ = ["BrowserProxyMode"]
+
+BrowserProxyMode: TypeAlias = Literal["direct", "default"]
diff --git a/src/kernel/types/browser_repl_content.py b/src/kernel/types/browser_repl_content.py
new file mode 100644
index 00000000..70af04d7
--- /dev/null
+++ b/src/kernel/types/browser_repl_content.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import Annotated, TypeAlias
+
+from .._utils import PropertyInfo
+from .browser_repl_text_content import BrowserReplTextContent
+from .browser_repl_image_content import BrowserReplImageContent
+
+__all__ = ["BrowserReplContent"]
+
+BrowserReplContent: TypeAlias = Annotated[
+ Union[BrowserReplTextContent, BrowserReplImageContent], PropertyInfo(discriminator="type")
+]
diff --git a/src/kernel/types/browser_repl_image_content.py b/src/kernel/types/browser_repl_image_content.py
new file mode 100644
index 00000000..4af25aa9
--- /dev/null
+++ b/src/kernel/types/browser_repl_image_content.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["BrowserReplImageContent"]
+
+
+class BrowserReplImageContent(BaseModel):
+ data_b64: str
+
+ mime_type: str
+
+ type: Literal["image"]
diff --git a/src/kernel/types/browser_repl_params.py b/src/kernel/types/browser_repl_params.py
new file mode 100644
index 00000000..28df3a41
--- /dev/null
+++ b/src/kernel/types/browser_repl_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["BrowserReplParams"]
+
+
+class BrowserReplParams(TypedDict, total=False):
+ code: Required[str]
+ """JavaScript evaluated in a persistent Node.js runtime.
+
+ Top-level bindings persist until the browser VM's API process exits, the REPL is
+ reset, or the REPL is terminated after a crash or timeout. Static top-level
+ imports are unsupported; use dynamic `import()`. Expression values are ignored;
+ emit output with `repl.write(...)`, console methods, or `repl.emitImage(...)`.
+ May be empty only when `reset` is true.
+ """
+
+ reset: bool
+ """Terminate the current REPL, start a fresh one, and then evaluate code."""
+
+ timeout_sec: int
+ """Maximum execution time in seconds. Default is 60."""
diff --git a/src/kernel/types/browser_repl_result.py b/src/kernel/types/browser_repl_result.py
new file mode 100644
index 00000000..d5815501
--- /dev/null
+++ b/src/kernel/types/browser_repl_result.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .._models import BaseModel
+from .browser_repl_content import BrowserReplContent
+
+__all__ = ["BrowserReplResult"]
+
+
+class BrowserReplResult(BaseModel):
+ """Result of Browser REPL code execution."""
+
+ repl_id: str
+ """CUID2 identifying the exact state-holding REPL process used for this execution.
+
+ Stable across calls and Chromium reconnects; changes after an API restart,
+ explicit reset, execution timeout, or REPL crash.
+ """
+
+ success: bool
+ """Whether the code executed successfully."""
+
+ content: Optional[List[BrowserReplContent]] = None
+ """Optional ordered text/image output produced by the execution."""
+
+ content_truncated: Optional[bool] = None
+ """True if text or image output was dropped or truncated due to response limits."""
+
+ duration_ms: Optional[int] = None
+ """Wall-clock execution time in milliseconds."""
+
+ error: Optional[str] = None
+ """Error message if execution failed."""
+
+ repl_terminated: Optional[bool] = None
+ """
+ True if the REPL identified by `repl_id` was terminated by this request. The
+ next request lazily starts a fresh REPL with a new `repl_id`.
+ """
+
+ stack: Optional[str] = None
+ """Stack trace if execution failed."""
diff --git a/src/kernel/types/browser_repl_text_content.py b/src/kernel/types/browser_repl_text_content.py
new file mode 100644
index 00000000..034d3691
--- /dev/null
+++ b/src/kernel/types/browser_repl_text_content.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["BrowserReplTextContent"]
+
+
+class BrowserReplTextContent(BaseModel):
+ channel: Literal["write", "stdout", "stderr"]
+ """
+ `write` is emitted by `repl.write`; `stdout` and `stderr` are emitted by console
+ methods.
+ """
+
+ text: str
+
+ type: Literal["text"]
diff --git a/src/kernel/types/browser_retrieve_response.py b/src/kernel/types/browser_retrieve_response.py
index 80a96d36..c537589f 100644
--- a/src/kernel/types/browser_retrieve_response.py
+++ b/src/kernel/types/browser_retrieve_response.py
@@ -1,14 +1,22 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, List, Optional
from datetime import datetime
+from typing_extensions import Literal
+from .tags import Tags
from .profile import Profile
from .._models import BaseModel
+from .browser_proxy import BrowserProxy
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_persistence import BrowserPersistence
+from .browser_video_memory import BrowserVideoMemory
+from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
+from .browsers.browser_telemetry_config import BrowserTelemetryConfig
__all__ = ["BrowserRetrieveResponse"]
@@ -23,6 +31,12 @@ class BrowserRetrieveResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region of the browser session. Fixed once the session is created."""
+
session_id: str
"""Unique identifier for the browser session"""
@@ -44,6 +58,13 @@ class BrowserRetrieveResponse(BaseModel):
Only available for non-headless browsers.
"""
+ chrome_policy: Optional[Dict[str, object]] = None
+ """
+ Custom Chrome enterprise policy overrides that were applied to this browser
+ session, if any. Echoed back for verification. Keys are Chrome enterprise policy
+ names.
+ """
+
deleted_at: Optional[datetime] = None
"""When the browser session was soft-deleted. Only present for deleted sessions."""
@@ -56,8 +77,17 @@ class BrowserRetrieveResponse(BaseModel):
kiosk_mode: Optional[bool] = None
"""Whether the browser session is running in kiosk mode."""
- persistence: Optional[BrowserPersistence] = None
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+ 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."""
+
+ network: Optional[BrowserNetworkConfig] = None
+ """Network configuration the session was created with, if any.
+
+ Omitted when the session has no network configuration.
+ """
pool: Optional[BrowserPoolRef] = None
"""Browser pool this session was acquired from, if any."""
@@ -65,30 +95,66 @@ class BrowserRetrieveResponse(BaseModel):
profile: Optional[Profile] = None
"""Browser profile metadata."""
+ profile_save_changes: Optional[bool] = None
+ """
+ Whether changes made during this browser session are saved back to its profile
+ when the session ends. Omitted when no profile is attached.
+ """
+
+ proxy: Optional[BrowserProxy] = None
+ """Resolved proxy configuration for this browser session."""
+
proxy_id: Optional[str] = None
- """ID of the proxy associated with this browser session, if any."""
+ """ID of the proxy associated with this browser session, if any.
+
+ Deprecated in favor of proxy.
+ """
start_url: Optional[str] = None
- """URL the session was asked to navigate to on creation, if any.
+ """URL the session was most recently asked to navigate to, if any.
+
+ Recorded for debugging. Navigation is fire-and-forget — the URL is dispatched to
+ the browser without waiting for it to load, and any errors (DNS failure, bad
+ status, timeout) are silently dropped. Captures what was requested, not what the
+ browser actually loaded.
+ """
+
+ tags: Optional[Tags] = None
+ """User-defined key-value tags that were set on this browser session, if any.
- Recorded for debugging — navigation is best-effort and may have failed.
+ Echoed back when present.
"""
+ telemetry: Optional[BrowserTelemetryConfig] = None
+ """Active telemetry configuration for the session, if any."""
+
usage: Optional[BrowserUsage] = None
"""Session usage metrics."""
+ usage_status: Optional[Literal["pending", "ready"]] = None
+ """Whether final usage billing is still pending or complete.
+
+ Only present for deleted sessions.
+ """
+
+ vaults: Optional[List[VaultReference]] = None
+ """Vaults linked when the browser session was created."""
+
+ video_memory: Optional[BrowserVideoMemory] = None
+ """Video memory (VRAM) of the GPU browser session. Only present when gpu is true."""
+
viewport: Optional[BrowserViewport] = None
"""
Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
diff --git a/src/kernel/types/browser_update_params.py b/src/kernel/types/browser_update_params.py
index e0f7588e..3be13135 100644
--- a/src/kernel/types/browser_update_params.py
+++ b/src/kernel/types/browser_update_params.py
@@ -5,35 +5,205 @@
from typing import Optional
from typing_extensions import TypedDict
+from .tags_param import TagsParam
+from .browser_proxy_config_param import BrowserProxyConfigParam
+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__ = ["BrowserUpdateParams", "Viewport"]
+__all__ = [
+ "BrowserUpdateParams",
+ "Telemetry",
+ "TelemetryExport",
+ "TelemetryExportOtlp",
+ "TelemetryExportOtlpDestination",
+ "TelemetryStorage",
+ "Viewport",
+]
class BrowserUpdateParams(TypedDict, total=False):
disable_default_proxy: bool
"""
If true, stealth browsers connect directly instead of using the default stealth
- proxy.
+ 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.
+
+ Omit to leave unchanged, set to an empty string to clear the name. When set,
+ must be unique among active sessions within the project.
+ """
+
+ network: BrowserNetworkUpdateParam
+ """Network configuration changes to apply to the running browser session."""
+
profile: BrowserProfile
"""Profile to load into the browser session.
Only allowed if the session does not already have a profile loaded.
"""
+ proxy: BrowserProxyConfigParam
+ """Proxy configuration to apply.
+
+ Omit to leave the current configuration unchanged. Cannot be combined with
+ proxy_id or disable_default_proxy. Set mode to direct to switch to direct egress
+ regardless of stealth. Set mode to default to restore the browser default after
+ using a selected proxy: Kernel's default stealth proxy for a stealth browser, or
+ direct egress for a non-stealth browser. Updating proxy does not change stealth
+ or CAPTCHA solver behavior.
+ """
+
proxy_id: Optional[str]
"""ID of the proxy to use.
- Omit to leave unchanged, set to empty string to remove proxy.
+ Omit to leave unchanged, set to empty string to remove proxy. Deprecated in
+ favor of proxy.
+ """
+
+ start_url: str
+ """Optional URL to navigate the browser to after applying this update.
+
+ When a profile is loaded in the same update, this overrides the profile's
+ restored tabs. Navigation is best-effort, so failures do not fail the update.
+ Omit or set to an empty string to leave the current page unchanged.
+ """
+
+ tags: Optional[TagsParam]
+ """User-defined key-value tags for the browser session.
+
+ Omit to leave unchanged. Provide a map to replace the entire tag set (full
+ replace, not a merge). Set to an empty object ({}) to clear all tags. Up to 50
+ pairs.
+ """
+
+ telemetry: Optional[Telemetry]
+ """Telemetry configuration.
+
+ Omit, set to null, or set to an empty object ({}) to leave the existing
+ configuration unchanged. Set enabled to true to enable capture using VM
+ defaults. Set enabled to false to stop capture. Provide browser category
+ settings for per-category updates. Explicitly disabling all four categories also
+ stops capture.
"""
viewport: Viewport
"""Viewport configuration to apply to the browser session."""
+class TelemetryExportOtlpDestination(TypedDict, total=False):
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ id: str
+ """OTLP destination ID"""
+
+ name: str
+ """OTLP destination name"""
+
+
+class TelemetryExportOtlp(TypedDict, total=False):
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+ destination: TelemetryExportOtlpDestination
+ """OTLP destination to export this session's captured telemetry to.
+
+ Provide either id or name. Requires telemetry capture to be enabled.
+ """
+
+ enabled: bool
+ """Whether to export captured telemetry over OTLP.
+
+ Setting destination implies enabled=true, so this only needs to be set
+ explicitly to disable export (enabled=false with a destination is rejected).
+ """
+
+
+class TelemetryExport(TypedDict, total=False):
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ otlp: TelemetryExportOtlp
+ """
+ Export captured telemetry over OTLP to one of the org's configured destinations.
+ """
+
+
+class TelemetryStorage(TypedDict, total=False):
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+ enabled: bool
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ Defaults to true. Setting false requires an OTLP destination and cannot be
+ changed after the browser is created.
+ """
+
+
+class Telemetry(TypedDict, total=False):
+ """Telemetry configuration.
+
+ Omit, set to null, or set to an empty object ({}) to leave the existing configuration unchanged. Set enabled to true to enable capture using VM defaults. Set enabled to false to stop capture. Provide browser category settings for per-category updates. Explicitly disabling all four categories also stops capture.
+ """
+
+ browser: BrowserTelemetryCategoriesConfigParam
+ """Per-category capture flags.
+
+ The operational categories (control, connection, system, captcha) are captured
+ whenever telemetry is enabled; set one to enabled=false to opt out. The CDP
+ categories (console, network, page, interaction), screenshot and platform are
+ off by default; set enabled=true to opt in. On create, provided categories layer
+ onto the default set. On update, provided categories merge onto the session's
+ current config; when no telemetry is active this falls back to the default set
+ (matching create). If browser is omitted or empty, the default set is used. A
+ browser config that disables every category stops capture on update and starts
+ no capture on create.
+ """
+
+ enabled: bool
+ """Request shortcut for browser telemetry capture.
+
+ True enables capture; with no browser category settings it captures the default
+ set (control, connection, system, captcha), and any browser category settings
+ are layered onto that default set. On update, enabled=true resolves the config
+ fresh from the default set plus any provided categories, replacing the session's
+ current selection rather than merging onto it; omit enabled to merge categories
+ onto the current selection instead. False stops capture on update and starts no
+ capture on create. enabled=false cannot be combined with browser category
+ settings.
+ """
+
+ export: TelemetryExport
+ """Where to export this session's captured telemetry.
+
+ Omit to capture without exporting.
+ """
+
+ storage: TelemetryStorage
+ """Whether to persist this session's captured telemetry to Kernel storage."""
+
+
class Viewport(BrowserViewport, total=False):
"""Viewport configuration to apply to the browser session."""
diff --git a/src/kernel/types/browser_update_response.py b/src/kernel/types/browser_update_response.py
index 4d1f61bc..5b9f8ae3 100644
--- a/src/kernel/types/browser_update_response.py
+++ b/src/kernel/types/browser_update_response.py
@@ -1,14 +1,22 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, List, Optional
from datetime import datetime
+from typing_extensions import Literal
+from .tags import Tags
from .profile import Profile
from .._models import BaseModel
+from .browser_proxy import BrowserProxy
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_persistence import BrowserPersistence
+from .browser_video_memory import BrowserVideoMemory
+from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
+from .browsers.browser_telemetry_config import BrowserTelemetryConfig
__all__ = ["BrowserUpdateResponse"]
@@ -23,6 +31,12 @@ class BrowserUpdateResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region of the browser session. Fixed once the session is created."""
+
session_id: str
"""Unique identifier for the browser session"""
@@ -44,6 +58,13 @@ class BrowserUpdateResponse(BaseModel):
Only available for non-headless browsers.
"""
+ chrome_policy: Optional[Dict[str, object]] = None
+ """
+ Custom Chrome enterprise policy overrides that were applied to this browser
+ session, if any. Echoed back for verification. Keys are Chrome enterprise policy
+ names.
+ """
+
deleted_at: Optional[datetime] = None
"""When the browser session was soft-deleted. Only present for deleted sessions."""
@@ -56,8 +77,17 @@ class BrowserUpdateResponse(BaseModel):
kiosk_mode: Optional[bool] = None
"""Whether the browser session is running in kiosk mode."""
- persistence: Optional[BrowserPersistence] = None
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+ 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."""
+
+ network: Optional[BrowserNetworkConfig] = None
+ """Network configuration the session was created with, if any.
+
+ Omitted when the session has no network configuration.
+ """
pool: Optional[BrowserPoolRef] = None
"""Browser pool this session was acquired from, if any."""
@@ -65,30 +95,66 @@ class BrowserUpdateResponse(BaseModel):
profile: Optional[Profile] = None
"""Browser profile metadata."""
+ profile_save_changes: Optional[bool] = None
+ """
+ Whether changes made during this browser session are saved back to its profile
+ when the session ends. Omitted when no profile is attached.
+ """
+
+ proxy: Optional[BrowserProxy] = None
+ """Resolved proxy configuration for this browser session."""
+
proxy_id: Optional[str] = None
- """ID of the proxy associated with this browser session, if any."""
+ """ID of the proxy associated with this browser session, if any.
+
+ Deprecated in favor of proxy.
+ """
start_url: Optional[str] = None
- """URL the session was asked to navigate to on creation, if any.
+ """URL the session was most recently asked to navigate to, if any.
+
+ Recorded for debugging. Navigation is fire-and-forget — the URL is dispatched to
+ the browser without waiting for it to load, and any errors (DNS failure, bad
+ status, timeout) are silently dropped. Captures what was requested, not what the
+ browser actually loaded.
+ """
+
+ tags: Optional[Tags] = None
+ """User-defined key-value tags that were set on this browser session, if any.
- Recorded for debugging — navigation is best-effort and may have failed.
+ Echoed back when present.
"""
+ telemetry: Optional[BrowserTelemetryConfig] = None
+ """Active telemetry configuration for the session, if any."""
+
usage: Optional[BrowserUsage] = None
"""Session usage metrics."""
+ usage_status: Optional[Literal["pending", "ready"]] = None
+ """Whether final usage billing is still pending or complete.
+
+ Only present for deleted sessions.
+ """
+
+ vaults: Optional[List[VaultReference]] = None
+ """Vaults linked when the browser session was created."""
+
+ video_memory: Optional[BrowserVideoMemory] = None
+ """Video memory (VRAM) of the GPU browser session. Only present when gpu is true."""
+
viewport: Optional[BrowserViewport] = None
"""
Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
diff --git a/src/kernel/types/browser_video_memory.py b/src/kernel/types/browser_video_memory.py
new file mode 100644
index 00000000..28c3569e
--- /dev/null
+++ b/src/kernel/types/browser_video_memory.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__ = ["BrowserVideoMemory"]
+
+BrowserVideoMemory: TypeAlias = Literal["2GiB", "4GiB"]
diff --git a/src/kernel/types/browsers/__init__.py b/src/kernel/types/browsers/__init__.py
index 1e47205d..27104cff 100644
--- a/src/kernel/types/browsers/__init__.py
+++ b/src/kernel/types/browsers/__init__.py
@@ -2,9 +2,19 @@
from __future__ import annotations
+from .tab import Tab as Tab
+from .tool import Tool as Tool
+from .tool_frame import ToolFrame as ToolFrame
+from .tool_source import ToolSource as ToolSource
from .f_move_params import FMoveParams as FMoveParams
+from .tool_metadata import ToolMetadata as ToolMetadata
+from .tools_response import ToolsResponse as ToolsResponse
from .f_upload_params import FUploadParams as FUploadParams
+from .tool_annotations import ToolAnnotations as ToolAnnotations
+from .invocation_result import InvocationResult as InvocationResult
from .log_stream_params import LogStreamParams as LogStreamParams
+from .browser_call_stack import BrowserCallStack as BrowserCallStack
+from .custom_tool_source import CustomToolSource as CustomToolSource
from .f_file_info_params import FFileInfoParams as FFileInfoParams
from .f_read_file_params import FReadFileParams as FReadFileParams
from .f_list_files_params import FListFilesParams as FListFilesParams
@@ -13,41 +23,116 @@
from .process_exec_params import ProcessExecParams as ProcessExecParams
from .process_kill_params import ProcessKillParams as ProcessKillParams
from .replay_start_params import ReplayStartParams as ReplayStartParams
+from .browser_event_source import BrowserEventSource as BrowserEventSource
+from .browser_http_headers import BrowserHTTPHeaders as BrowserHTTPHeaders
from .f_delete_file_params import FDeleteFileParams as FDeleteFileParams
from .f_file_info_response import FFileInfoResponse as FFileInfoResponse
from .process_spawn_params import ProcessSpawnParams as ProcessSpawnParams
from .process_stdin_params import ProcessStdinParams as ProcessStdinParams
from .replay_list_response import ReplayListResponse as ReplayListResponse
+from .browser_event_context import BrowserEventContext as BrowserEventContext
from .computer_batch_params import ComputerBatchParams as ComputerBatchParams
from .f_list_files_response import FListFilesResponse as FListFilesResponse
from .process_exec_response import ProcessExecResponse as ProcessExecResponse
from .process_kill_response import ProcessKillResponse as ProcessKillResponse
from .process_resize_params import ProcessResizeParams as ProcessResizeParams
from .replay_start_response import ReplayStartResponse as ReplayStartResponse
+from .browser_api_call_event import BrowserAPICallEvent as BrowserAPICallEvent
+from .browser_page_lcp_event import BrowserPageLcpEvent as BrowserPageLcpEvent
from .computer_scroll_params import ComputerScrollParams as ComputerScrollParams
from .process_spawn_response import ProcessSpawnResponse as ProcessSpawnResponse
from .process_stdin_response import ProcessStdinResponse as ProcessStdinResponse
+from .browser_page_load_event import BrowserPageLoadEvent as BrowserPageLoadEvent
+from .browser_telemetry_event import BrowserTelemetryEvent as BrowserTelemetryEvent
from .process_resize_response import ProcessResizeResponse as ProcessResizeResponse
from .process_status_response import ProcessStatusResponse as ProcessStatusResponse
+from .telemetry_events_params import TelemetryEventsParams as TelemetryEventsParams
+from .telemetry_stream_params import TelemetryStreamParams as TelemetryStreamParams
+from .browser_telemetry_config import BrowserTelemetryConfig as BrowserTelemetryConfig
+from .webmcp_list_tools_params import WebmcpListToolsParams as WebmcpListToolsParams
+from .browser_cdp_command_event import BrowserCdpCommandEvent as BrowserCdpCommandEvent
+from .browser_cdp_connect_event import BrowserCdpConnectEvent as BrowserCdpConnectEvent
+from .browser_console_log_event import BrowserConsoleLogEvent as BrowserConsoleLogEvent
+from .browser_proxy_error_event import BrowserProxyErrorEvent as BrowserProxyErrorEvent
from .computer_press_key_params import ComputerPressKeyParams as ComputerPressKeyParams
from .computer_type_text_params import ComputerTypeTextParams as ComputerTypeTextParams
from .f_create_directory_params import FCreateDirectoryParams as FCreateDirectoryParams
from .f_delete_directory_params import FDeleteDirectoryParams as FDeleteDirectoryParams
from .f_download_dir_zip_params import FDownloadDirZipParams as FDownloadDirZipParams
from .playwright_execute_params import PlaywrightExecuteParams as PlaywrightExecuteParams
+from .telemetry_events_response import TelemetryEventsResponse as TelemetryEventsResponse
+from .telemetry_stream_response import TelemetryStreamResponse as TelemetryStreamResponse
+from .webmcp_invoke_tool_params import WebmcpInvokeToolParams as WebmcpInvokeToolParams
+from .browser_cdp_command_method import BrowserCdpCommandMethod as BrowserCdpCommandMethod
+from .browser_network_idle_event import BrowserNetworkIdleEvent as BrowserNetworkIdleEvent
+from .browser_page_crashed_event import BrowserPageCrashedEvent as BrowserPageCrashedEvent
from .computer_drag_mouse_params import ComputerDragMouseParams as ComputerDragMouseParams
from .computer_move_mouse_params import ComputerMoveMouseParams as ComputerMoveMouseParams
+from .browser_console_error_event import BrowserConsoleErrorEvent as BrowserConsoleErrorEvent
from .computer_click_mouse_params import ComputerClickMouseParams as ComputerClickMouseParams
from .playwright_execute_response import PlaywrightExecuteResponse as PlaywrightExecuteResponse
+from .browser_cdp_disconnect_event import BrowserCdpDisconnectEvent as BrowserCdpDisconnectEvent
+from .browser_interaction_key_event import BrowserInteractionKeyEvent as BrowserInteractionKeyEvent
+from .browser_network_request_event import BrowserNetworkRequestEvent as BrowserNetworkRequestEvent
+from .browser_page_navigation_event import BrowserPageNavigationEvent as BrowserPageNavigationEvent
+from .browser_page_tab_opened_event import BrowserPageTabOpenedEvent as BrowserPageTabOpenedEvent
+from .browser_service_crashed_event import BrowserServiceCrashedEvent as BrowserServiceCrashedEvent
+from .browser_system_oom_kill_event import BrowserSystemOomKillEvent as BrowserSystemOomKillEvent
from .f_set_file_permissions_params import FSetFilePermissionsParams as FSetFilePermissionsParams
+from .browser_network_response_event import BrowserNetworkResponseEvent as BrowserNetworkResponseEvent
from .process_stdout_stream_response import ProcessStdoutStreamResponse as ProcessStdoutStreamResponse
+from .browser_interaction_click_event import BrowserInteractionClickEvent as BrowserInteractionClickEvent
+from .browser_live_view_connect_event import BrowserLiveViewConnectEvent as BrowserLiveViewConnectEvent
+from .browser_page_layout_shift_event import BrowserPageLayoutShiftEvent as BrowserPageLayoutShiftEvent
+from .browser_platform_api_call_event import BrowserPlatformAPICallEvent as BrowserPlatformAPICallEvent
+from .browser_telemetry_export_config import BrowserTelemetryExportConfig as BrowserTelemetryExportConfig
from .computer_write_clipboard_params import ComputerWriteClipboardParams as ComputerWriteClipboardParams
+from .browser_monitor_screenshot_event import BrowserMonitorScreenshotEvent as BrowserMonitorScreenshotEvent
+from .browser_telemetry_control_config import BrowserTelemetryControlConfig as BrowserTelemetryControlConfig
+from .browser_telemetry_storage_config import BrowserTelemetryStorageConfig as BrowserTelemetryStorageConfig
from .computer_read_clipboard_response import ComputerReadClipboardResponse as ComputerReadClipboardResponse
+from .browser_monitor_init_failed_event import BrowserMonitorInitFailedEvent as BrowserMonitorInitFailedEvent
+from .browser_monitor_reconnected_event import BrowserMonitorReconnectedEvent as BrowserMonitorReconnectedEvent
+from .browser_page_layout_settled_event import BrowserPageLayoutSettledEvent as BrowserPageLayoutSettledEvent
+from .browser_telemetry_category_config import BrowserTelemetryCategoryConfig as BrowserTelemetryCategoryConfig
+from .browser_captcha_solve_result_event import BrowserCaptchaSolveResultEvent as BrowserCaptchaSolveResultEvent
+from .browser_live_view_disconnect_event import BrowserLiveViewDisconnectEvent as BrowserLiveViewDisconnectEvent
+from .browser_monitor_disconnected_event import BrowserMonitorDisconnectedEvent as BrowserMonitorDisconnectedEvent
from .computer_capture_screenshot_params import ComputerCaptureScreenshotParams as ComputerCaptureScreenshotParams
+from .browser_captcha_solve_started_event import BrowserCaptchaSolveStartedEvent as BrowserCaptchaSolveStartedEvent
+from .browser_telemetry_categories_config import BrowserTelemetryCategoriesConfig as BrowserTelemetryCategoriesConfig
+from .browser_network_loading_failed_event import BrowserNetworkLoadingFailedEvent as BrowserNetworkLoadingFailedEvent
+from .browser_telemetry_cdp_control_config import BrowserTelemetryCdpControlConfig as BrowserTelemetryCdpControlConfig
+from .browser_telemetry_otlp_export_config import BrowserTelemetryOtlpExportConfig as BrowserTelemetryOtlpExportConfig
from .computer_get_mouse_position_response import ComputerGetMousePositionResponse as ComputerGetMousePositionResponse
+from .browser_page_dom_content_loaded_event import BrowserPageDomContentLoadedEvent as BrowserPageDomContentLoadedEvent
+from .browser_page_navigation_settled_event import (
+ BrowserPageNavigationSettledEvent as BrowserPageNavigationSettledEvent,
+)
from .computer_set_cursor_visibility_params import (
ComputerSetCursorVisibilityParams as ComputerSetCursorVisibilityParams,
)
+from .browser_captcha_challenge_result_event import (
+ BrowserCaptchaChallengeResultEvent as BrowserCaptchaChallengeResultEvent,
+)
+from .browser_monitor_reconnect_failed_event import (
+ BrowserMonitorReconnectFailedEvent as BrowserMonitorReconnectFailedEvent,
+)
+from .browser_telemetry_control_config_param import (
+ BrowserTelemetryControlConfigParam as BrowserTelemetryControlConfigParam,
+)
+from .browser_telemetry_category_config_param import (
+ BrowserTelemetryCategoryConfigParam as BrowserTelemetryCategoryConfigParam,
+)
from .computer_set_cursor_visibility_response import (
ComputerSetCursorVisibilityResponse as ComputerSetCursorVisibilityResponse,
)
+from .browser_interaction_scroll_settled_event import (
+ BrowserInteractionScrollSettledEvent as BrowserInteractionScrollSettledEvent,
+)
+from .browser_telemetry_categories_config_param import (
+ BrowserTelemetryCategoriesConfigParam as BrowserTelemetryCategoriesConfigParam,
+)
+from .browser_telemetry_cdp_control_config_param import (
+ BrowserTelemetryCdpControlConfigParam as BrowserTelemetryCdpControlConfigParam,
+)
diff --git a/src/kernel/types/browsers/browser_api_call_event.py b/src/kernel/types/browsers/browser_api_call_event.py
new file mode 100644
index 00000000..b4210968
--- /dev/null
+++ b/src/kernel/types/browsers/browser_api_call_event.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserAPICallEvent", "Data"]
+
+
+class Data(BaseModel):
+ duration_ms: float
+ """Wall-clock duration of the handler in milliseconds."""
+
+ operation_id: str
+ """Matched route's operation, named as the in-VM API names its handler (e.g.
+
+ ProcessExec, TakeScreenshot).
+ """
+
+ request_id: str
+ """Per-request identifier from the in-VM API request middleware."""
+
+ status: int
+ """HTTP response status code."""
+
+ code: Optional[str] = None
+ """
+ Source submitted to the Playwright code-execution endpoint, capped at 8192 bytes
+ like every other captured string. A capped value is cut on a character boundary
+ and ends in `...[truncated]`. Absent for every other operation.
+ """
+
+
+class BrowserAPICallEvent(BaseModel):
+ """
+ An agent-driven HTTP call that drives the browser, handled by the in-VM API server. Calls that manage the VM instead emit platform_api_call.
+ """
+
+ category: Literal["control"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["api_call"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_call_stack.py b/src/kernel/types/browsers/browser_call_stack.py
new file mode 100644
index 00000000..d9e97ffd
--- /dev/null
+++ b/src/kernel/types/browsers/browser_call_stack.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["BrowserCallStack", "CallFrame"]
+
+
+class CallFrame(BaseModel):
+ column_number: int = FieldInfo(alias="columnNumber")
+ """Zero-based column number within the line."""
+
+ function_name: str = FieldInfo(alias="functionName")
+ """JavaScript function name, or empty string for anonymous functions."""
+
+ line_number: int = FieldInfo(alias="lineNumber")
+ """Zero-based line number within the script."""
+
+ script_id: str = FieldInfo(alias="scriptId")
+ """CDP script identifier."""
+
+ url: str
+ """URL or name of the script file."""
+
+
+class BrowserCallStack(BaseModel):
+ """
+ CDP Runtime.StackTrace representing the JavaScript call stack at the time of an event. Fields use CDP naming conventions rather than snake_case to match the Chrome DevTools Protocol wire format.
+ """
+
+ call_frames: List[CallFrame] = FieldInfo(alias="callFrames")
+ """Ordered list of call frames, outermost first."""
+
+ description: Optional[str] = None
+ """Optional label for the stack trace (e.g. async cause)."""
+
+ parent: Optional["BrowserCallStack"] = None
+ """Parent stack trace for async stacks."""
diff --git a/src/kernel/types/browsers/browser_captcha_challenge_result_event.py b/src/kernel/types/browsers/browser_captcha_challenge_result_event.py
new file mode 100644
index 00000000..00580a46
--- /dev/null
+++ b/src/kernel/types/browsers/browser_captcha_challenge_result_event.py
@@ -0,0 +1,112 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserCaptchaChallengeResultEvent", "Data"]
+
+
+class Data(BaseModel):
+ """An observed challenge emits one outcome across any number of solver tasks.
+
+ For eligible providers without a widget observer, each successful token task emits an inferred solved result instead; multiple such results may belong to one challenge. Failed tasks and image_challenge rounds produce no inferred result. Exactly one of challenge_id or task_id is present: challenge_id on an observed result, task_id on an inferred one. A challenge whose tasks all fail produces task events only, so consumers fall back to captcha_solve_result for it.
+ """
+
+ captcha_type: Literal["hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "press_and_hold", "other"]
+ """Deprecated: use captcha_provider."""
+
+ duration_ms: float
+ """
+ Wall-clock duration from the challenge appearing to its terminal outcome,
+ covering every solver attempt in between. For an inferred result, the duration
+ of its solver task.
+ """
+
+ status: Literal["solved", "failure", "timeout", "abandoned"]
+ """Terminal outcome of a challenge.
+
+ solved: the page observed the challenge clear after a solver attempt, or an
+ inferred result reports a token for the whole widget without page observation.
+ failure: a terminal solver failure occurred, or all attempts ended while the
+ challenge remained. timeout: the challenge-level wait budget expired while the
+ challenge remained. abandoned: observation ended without an attributable
+ terminal challenge outcome. This includes a dismissed widget or page unload
+ without a solved signal or terminal solver outcome, and a token appearing while
+ multiple same-provider challenges are open, because the producer cannot
+ attribute that token to this visible challenge. A captcha_solve_result with the
+ same challenge_id may therefore report success while the challenge result
+ reports abandoned. A solved challenge does not prove the site accepted the token
+ or that the guarded action succeeded.
+ """
+
+ captcha_provider: Optional[
+ Literal[
+ "hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "arkose", "human", "binance", "other"
+ ]
+ ] = None
+ """Captcha product the challenge belongs to, not the service that solved it.
+
+ Enterprise reCAPTCHA variants are grouped into their version bucket
+ (recaptcha_v2 or recaptcha_v3), FunCaptcha uses arkose, press-and-hold
+ challenges served by HUMAN (formerly PerimeterX) use human, and unlisted
+ products, including text captchas a site draws itself, use other.
+ """
+
+ challenge_id: Optional[str] = None
+ """Opaque identifier shared by events for one visible challenge.
+
+ An image-grid captcha may create multiple task_id values for one challenge_id.
+ The same value may continue across a page reload when the challenge episode
+ continues. It does not indicate task ordering or challenge completion.
+ """
+
+ inferred: Optional[bool] = None
+ """
+ True when the relay derived this result from a successful token task without
+ observing the page. An inferred result has task_id instead of challenge_id.
+ Absent on page-observed results.
+ """
+
+ task_id: Optional[str] = None
+ """The task_id of the solver task an inferred result was derived from.
+
+ Join on it to pair the result with that task's captcha_solve_started and
+ captcha_solve_result. Present only when inferred is true.
+ """
+
+ website_host: Optional[str] = None
+ """Host of the page where the challenge appeared."""
+
+ website_path: Optional[str] = None
+ """Path of the page where the challenge appeared. Query string excluded."""
+
+
+class BrowserCaptchaChallengeResultEvent(BaseModel):
+ """A captcha challenge reached an observed or inferred terminal outcome."""
+
+ category: Literal["captcha"]
+
+ data: Data
+ """An observed challenge emits one outcome across any number of solver tasks.
+
+ For eligible providers without a widget observer, each successful token task
+ emits an inferred solved result instead; multiple such results may belong to one
+ challenge. Failed tasks and image_challenge rounds produce no inferred result.
+ Exactly one of challenge_id or task_id is present: challenge_id on an observed
+ result, task_id on an inferred one. A challenge whose tasks all fail produces
+ task events only, so consumers fall back to captcha_solve_result for it.
+ """
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["captcha_challenge_result"]
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_captcha_solve_result_event.py b/src/kernel/types/browsers/browser_captcha_solve_result_event.py
new file mode 100644
index 00000000..5d7a7360
--- /dev/null
+++ b/src/kernel/types/browsers/browser_captcha_solve_result_event.py
@@ -0,0 +1,90 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserCaptchaSolveResultEvent", "Data"]
+
+
+class Data(BaseModel):
+ captcha_type: Literal["hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "press_and_hold", "other"]
+ """Deprecated: use captcha_provider and task_kind."""
+
+ duration_ms: float
+ """Wall-clock duration from solve start to terminal outcome.
+
+ Authoritative solve timing; do not derive it from the gap to a
+ captcha_solve_started event, whose delivery and ordering are not guaranteed.
+ """
+
+ status: Literal["success", "failure", "timeout", "abandoned"]
+ """Terminal outcome.
+
+ success: solver returned a usable solution. failure: solver returned an error
+ (see error_code). timeout: solver did not return within the caller's wait
+ budget. abandoned: caller cancelled or the page navigated away mid-solve.
+ """
+
+ captcha_provider: Optional[
+ Literal[
+ "hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "arkose", "human", "binance", "other"
+ ]
+ ] = None
+ """Captcha product the challenge belongs to, not the service that solved it.
+
+ Enterprise reCAPTCHA variants are grouped into their version bucket
+ (recaptcha_v2 or recaptcha_v3), FunCaptcha uses arkose, press-and-hold
+ challenges served by HUMAN (formerly PerimeterX) use human, and unlisted
+ products, including text captchas a site draws itself, use other.
+ """
+
+ challenge_id: Optional[str] = None
+ """Opaque identifier shared by events for one visible challenge.
+
+ An image-grid captcha may create multiple task_id values for one challenge_id.
+ The same value may continue across a page reload when the challenge episode
+ continues. It does not indicate task ordering or challenge completion.
+ """
+
+ error_code: Optional[str] = None
+ """Solver-specific error code on failure (e.g.
+
+ ERROR_CAPTCHA_UNSOLVABLE). Absent on success.
+ """
+
+ media: Optional[Literal["image", "video"]] = None
+ """Captured hCaptcha recognition media, when known."""
+
+ task_id: Optional[str] = None
+ """Opaque identifier shared with the matching captcha_solve_started."""
+
+ task_kind: Optional[Literal["token", "image_challenge", "press_and_hold"]] = None
+ """What the solver task produces. Absent when the producer cannot tell."""
+
+ website_host: Optional[str] = None
+ """Host of the page where the captcha was solved."""
+
+ website_path: Optional[str] = None
+ """Path of the page where the captcha was solved. Query string excluded."""
+
+
+class BrowserCaptchaSolveResultEvent(BaseModel):
+ """A captcha solve attempt reached a terminal outcome."""
+
+ category: Literal["captcha"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["captcha_solve_result"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_captcha_solve_started_event.py b/src/kernel/types/browsers/browser_captcha_solve_started_event.py
new file mode 100644
index 00000000..c17b9097
--- /dev/null
+++ b/src/kernel/types/browsers/browser_captcha_solve_started_event.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserCaptchaSolveStartedEvent", "Data"]
+
+
+class Data(BaseModel):
+ """Per-task payload.
+
+ A visible challenge may create multiple tasks. When present, task_id correlates this event with a captcha_solve_result, while challenge_id groups tasks from the same challenge. Events may arrive out of order or be absent, so their arrival does not indicate current solve state.
+ """
+
+ captcha_type: Literal["hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "press_and_hold", "other"]
+ """Deprecated: use captcha_provider and task_kind."""
+
+ captcha_provider: Optional[
+ Literal[
+ "hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "arkose", "human", "binance", "other"
+ ]
+ ] = None
+ """Captcha product the challenge belongs to, not the service that solved it.
+
+ Enterprise reCAPTCHA variants are grouped into their version bucket
+ (recaptcha_v2 or recaptcha_v3), FunCaptcha uses arkose, press-and-hold
+ challenges served by HUMAN (formerly PerimeterX) use human, and unlisted
+ products, including text captchas a site draws itself, use other.
+ """
+
+ challenge_id: Optional[str] = None
+ """Opaque identifier shared by events for one visible challenge.
+
+ An image-grid captcha may create multiple task_id values for one challenge_id.
+ The same value may continue across a page reload when the challenge episode
+ continues. It does not indicate task ordering or challenge completion.
+ """
+
+ media: Optional[Literal["image", "video"]] = None
+ """Captured hCaptcha recognition media, when known."""
+
+ task_id: Optional[str] = None
+ """Opaque identifier shared with the matching captcha_solve_result."""
+
+ task_kind: Optional[Literal["token", "image_challenge", "press_and_hold"]] = None
+ """What the solver task produces. Absent when the producer cannot tell."""
+
+ website_host: Optional[str] = None
+ """Host of the page where the captcha is being solved.
+
+ May be empty for solver tasks that carry no page URL.
+ """
+
+ website_path: Optional[str] = None
+ """Path of the page where the captcha is being solved. Query string excluded."""
+
+
+class BrowserCaptchaSolveStartedEvent(BaseModel):
+ """A captcha solver accepted a task."""
+
+ category: Literal["captcha"]
+
+ data: Data
+ """Per-task payload.
+
+ A visible challenge may create multiple tasks. When present, task_id correlates
+ this event with a captcha_solve_result, while challenge_id groups tasks from the
+ same challenge. Events may arrive out of order or be absent, so their arrival
+ does not indicate current solve state.
+ """
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["captcha_solve_started"]
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_cdp_command_event.py b/src/kernel/types/browsers/browser_cdp_command_event.py
new file mode 100644
index 00000000..045be40f
--- /dev/null
+++ b/src/kernel/types/browsers/browser_cdp_command_event.py
@@ -0,0 +1,1883 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = [
+ "BrowserCdpCommandEvent",
+ "Data",
+ "DataBrowserCdpInputDispatchMouseEventCommandData",
+ "DataBrowserCdpInputDispatchKeyEventCommandData",
+ "DataBrowserCdpInputInsertTextCommandData",
+ "DataBrowserCdpInputImeSetCompositionCommandData",
+ "DataBrowserCdpInputDispatchTouchEventCommandData",
+ "DataBrowserCdpInputDispatchDragEventCommandData",
+ "DataBrowserCdpInputCancelDraggingCommandData",
+ "DataBrowserCdpInputEmulateTouchFromMouseEventCommandData",
+ "DataBrowserCdpInputSynthesizePinchGestureCommandData",
+ "DataBrowserCdpInputSynthesizeScrollGestureCommandData",
+ "DataBrowserCdpInputSynthesizeTapGestureCommandData",
+ "DataBrowserCdpDomSetFileInputFilesCommandData",
+ "DataBrowserCdpDomFocusCommandData",
+ "DataBrowserCdpDomScrollIntoViewIfNeededCommandData",
+ "DataBrowserCdpPageBringToFrontCommandData",
+ "DataBrowserCdpPageCaptureScreenshotCommandData",
+ "DataBrowserCdpPageCaptureSnapshotCommandData",
+ "DataBrowserCdpPageHandleJavaScriptDialogCommandData",
+ "DataBrowserCdpPageNavigateCommandData",
+ "DataBrowserCdpPageNavigateToHistoryEntryCommandData",
+ "DataBrowserCdpPageReloadCommandData",
+ "DataBrowserCdpPagePrintToPdfCommandData",
+ "DataBrowserCdpPageStartScreencastCommandData",
+ "DataBrowserCdpPageStopScreencastCommandData",
+ "DataBrowserCdpPageStopLoadingCommandData",
+ "DataBrowserCdpPageCloseCommandData",
+ "DataBrowserCdpPageSetWebLifecycleStateCommandData",
+ "DataBrowserCdpTargetActivateTargetCommandData",
+ "DataBrowserCdpTargetCloseTargetCommandData",
+ "DataBrowserCdpTargetCreateTargetCommandData",
+ "DataBrowserCdpTargetCreateBrowserContextCommandData",
+ "DataBrowserCdpTargetDisposeBrowserContextCommandData",
+ "DataBrowserCdpTargetOpenDevToolsCommandData",
+ "DataBrowserCdpBrowserCancelDownloadCommandData",
+ "DataBrowserCdpBrowserCloseCommandData",
+ "DataBrowserCdpBrowserSetWindowBoundsCommandData",
+ "DataBrowserCdpBrowserSetContentsSizeCommandData",
+ "DataBrowserCdpAutofillTriggerCommandData",
+]
+
+
+class DataBrowserCdpInputDispatchMouseEventCommandData(BaseModel):
+ """Sanitized `Input.dispatchMouseEvent` arguments.
+
+ Canonical input: `Input.dispatchMouseEvent` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ event_type: Literal["mousePressed", "mouseReleased", "mouseMoved", "mouseWheel", "other"]
+ """Mouse event phase: `mousePressed`, `mouseReleased`, `mouseMoved` or
+ `mouseWheel`.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ method: Literal["Input.dispatchMouseEvent"]
+
+ button: Optional[Literal["none", "left", "middle", "right", "back", "forward", "other"]] = None
+ """
+ Button named by the command (`none`, `left`, `middle`, `right`, `back`,
+ `forward`). A value the protocol does not define is reported as `other`.
+ """
+
+ buttons: Optional[int] = None
+ """Bit field of buttons held down.
+
+ Non-zero on a `mouseMoved` means the move is a drag path.
+ """
+
+ click_count: Optional[int] = None
+ """Number of times the button was clicked (2 is a double click)."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ delta_x: Optional[float] = None
+ """Horizontal scroll delta, for `mouseWheel`."""
+
+ delta_y: Optional[float] = None
+ """Vertical scroll delta, for `mouseWheel`."""
+
+ force: Optional[float] = None
+ """Normalized pressure, 0 to 1."""
+
+ modifiers: Optional[int] = None
+ """Bit field of held modifier keys (1=Alt, 2=Ctrl, 4=Meta, 8=Shift)."""
+
+ pointer_type: Optional[Literal["mouse", "pen", "other"]] = None
+ """Pointer that generated the event (`mouse` or `pen`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ tangential_pressure: Optional[float] = None
+ """Normalized tangential pressure, -1 to 1."""
+
+ tilt_x: Optional[float] = None
+ """Pen tilt from the Y-Z plane, in degrees."""
+
+ tilt_y: Optional[float] = None
+ """Pen tilt from the X-Z plane, in degrees."""
+
+ twist: Optional[int] = None
+ """Pen clockwise rotation, in degrees."""
+
+ x: Optional[float] = None
+ """Viewport x coordinate in CSS pixels."""
+
+ y: Optional[float] = None
+ """Viewport y coordinate in CSS pixels."""
+
+
+class DataBrowserCdpInputDispatchKeyEventCommandData(BaseModel):
+ """Sanitized `Input.dispatchKeyEvent` arguments.
+
+ Canonical input: `Input.dispatchKeyEvent` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ event_type: Literal["keyDown", "keyUp", "rawKeyDown", "char", "other"]
+ """Key event phase: `keyDown`, `keyUp`, `rawKeyDown` or `char`.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ method: Literal["Input.dispatchKeyEvent"]
+
+ auto_repeat: Optional[bool] = None
+ """Whether the event was generated by key repeat."""
+
+ command_count: Optional[int] = None
+ """Number of editing commands (e.g. `selectAll`) carried by the event."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ is_keypad: Optional[bool] = None
+ """Whether the key is on the numeric keypad."""
+
+ is_system_key: Optional[bool] = None
+ """Whether the event is a system key event."""
+
+ location: Optional[int] = None
+ """Keyboard location (1=left, 2=right, 3=numpad)."""
+
+ modifiers: Optional[int] = None
+ """Bit field of held modifier keys (1=Alt, 2=Ctrl, 4=Meta, 8=Shift)."""
+
+ named_key: Optional[str] = None
+ """Key that commands the page rather than typing into it (e.g.
+
+ `Enter`, `Tab`, `ArrowDown`, `F5`). Keys that produce a character are never
+ captured; those are counted by `text_length`.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ text_length: Optional[int] = None
+ """Number of characters the command submitted. The text itself is never captured."""
+
+
+class DataBrowserCdpInputInsertTextCommandData(BaseModel):
+ """Sanitized `Input.insertText` arguments.
+
+ Canonical input: `Input.insertText` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Input.insertText"]
+
+ text_length: int
+ """Number of characters inserted. The text itself is never captured."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpInputImeSetCompositionCommandData(BaseModel):
+ """Sanitized `Input.imeSetComposition` arguments.
+
+ Canonical input: `Input.imeSetComposition` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Input.imeSetComposition"]
+
+ text_length: int
+ """Number of characters in the composition. The text itself is never captured."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ replacement_end: Optional[int] = None
+ """Replacement range end offset."""
+
+ replacement_start: Optional[int] = None
+ """Replacement range start offset."""
+
+ selection_end: Optional[int] = None
+ """Selection end offset within the composition."""
+
+ selection_start: Optional[int] = None
+ """Selection start offset within the composition."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpInputDispatchTouchEventCommandData(BaseModel):
+ """Sanitized `Input.dispatchTouchEvent` arguments.
+
+ Canonical input: `Input.dispatchTouchEvent` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ event_type: Literal["touchStart", "touchEnd", "touchMove", "touchCancel", "other"]
+ """Touch event phase: `touchStart`, `touchEnd`, `touchMove` or `touchCancel`.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ method: Literal["Input.dispatchTouchEvent"]
+
+ touch_point_count: int
+ """Number of active touch points the command carried."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ force: Optional[float] = None
+ """Normalized pressure of the first touch point, 0 to 1."""
+
+ modifiers: Optional[int] = None
+ """Bit field of held modifier keys (1=Alt, 2=Ctrl, 4=Meta, 8=Shift)."""
+
+ radius_x: Optional[float] = None
+ """Horizontal radius of the first touch point."""
+
+ radius_y: Optional[float] = None
+ """Vertical radius of the first touch point."""
+
+ rotation_angle: Optional[float] = None
+ """Rotation of the first touch point, in degrees."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ tangential_pressure: Optional[float] = None
+ """Normalized tangential pressure of the first touch point, -1 to 1."""
+
+ tilt_x: Optional[float] = None
+ """Tilt of the first touch point from the Y-Z plane, in degrees."""
+
+ tilt_y: Optional[float] = None
+ """Tilt of the first touch point from the X-Z plane, in degrees."""
+
+ twist: Optional[int] = None
+ """Clockwise rotation of the first touch point, in degrees."""
+
+ x: Optional[float] = None
+ """Viewport x coordinate of the first touch point.
+
+ Touch coordinates live inside `touchPoints`, so this is the primary point rather
+ than a command-level argument.
+ """
+
+ y: Optional[float] = None
+ """Viewport y coordinate of the first touch point."""
+
+
+class DataBrowserCdpInputDispatchDragEventCommandData(BaseModel):
+ """Sanitized `Input.dispatchDragEvent` arguments.
+
+ Canonical input: `Input.dispatchDragEvent` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ event_type: Literal["dragEnter", "dragOver", "drop", "dragCancel", "other"]
+ """Drag event phase: `dragEnter`, `dragOver`, `drop` or `dragCancel`.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ method: Literal["Input.dispatchDragEvent"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ drag_file_count: Optional[int] = None
+ """Number of files in the drag payload. File paths are never captured."""
+
+ drag_item_count: Optional[int] = None
+ """Number of items in the drag payload. Item contents are never captured."""
+
+ drag_mime_categories: Optional[
+ List[
+ Literal["text", "image", "audio", "video", "application", "font", "model", "multipart", "message", "other"]
+ ]
+ ] = None
+ """Distinct top-level MIME categories of the drag items (e.g.
+
+ `text`, `image`, `application`). Subtypes and contents are never captured. A
+ value the protocol does not define is reported as `other`.
+ """
+
+ drag_operations_mask: Optional[int] = None
+ """Bit field of allowed drag operations (1=copy, 2=link, 16=move)."""
+
+ modifiers: Optional[int] = None
+ """Bit field of held modifier keys (1=Alt, 2=Ctrl, 4=Meta, 8=Shift)."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ x: Optional[float] = None
+ """Viewport x coordinate in CSS pixels."""
+
+ y: Optional[float] = None
+ """Viewport y coordinate in CSS pixels."""
+
+
+class DataBrowserCdpInputCancelDraggingCommandData(BaseModel):
+ """Sanitized `Input.cancelDragging` arguments.
+
+ Canonical input: `Input.cancelDragging` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Input.cancelDragging"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpInputEmulateTouchFromMouseEventCommandData(BaseModel):
+ """Sanitized `Input.emulateTouchFromMouseEvent` arguments.
+
+ Canonical input: `Input.emulateTouchFromMouseEvent` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ event_type: Literal["mousePressed", "mouseReleased", "mouseMoved", "mouseWheel", "other"]
+ """Mouse event phase being emulated as touch.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ method: Literal["Input.emulateTouchFromMouseEvent"]
+
+ button: Optional[Literal["none", "left", "middle", "right", "back", "forward", "other"]] = None
+ """Button named by the command.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ click_count: Optional[int] = None
+ """Number of times the button was clicked."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ delta_x: Optional[float] = None
+ """Horizontal scroll delta."""
+
+ delta_y: Optional[float] = None
+ """Vertical scroll delta."""
+
+ modifiers: Optional[int] = None
+ """Bit field of held modifier keys (1=Alt, 2=Ctrl, 4=Meta, 8=Shift)."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ x: Optional[float] = None
+ """Viewport x coordinate in CSS pixels."""
+
+ y: Optional[float] = None
+ """Viewport y coordinate in CSS pixels."""
+
+
+class DataBrowserCdpInputSynthesizePinchGestureCommandData(BaseModel):
+ """Sanitized `Input.synthesizePinchGesture` arguments.
+
+ Canonical input: `Input.synthesizePinchGesture` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Input.synthesizePinchGesture"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ gesture_source_type: Optional[Literal["default", "touch", "mouse", "other"]] = None
+ """Input source the synthesized gesture emulates.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ relative_speed: Optional[int] = None
+ """Relative pointer speed, in pixels per second."""
+
+ scale_factor: Optional[float] = None
+ """Relative scale of the pinch (>1 zooms in)."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ x: Optional[float] = None
+ """Viewport x coordinate in CSS pixels."""
+
+ y: Optional[float] = None
+ """Viewport y coordinate in CSS pixels."""
+
+
+class DataBrowserCdpInputSynthesizeScrollGestureCommandData(BaseModel):
+ """Sanitized `Input.synthesizeScrollGesture` arguments.
+
+ Canonical input: `Input.synthesizeScrollGesture` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Input.synthesizeScrollGesture"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ gesture_source_type: Optional[Literal["default", "touch", "mouse", "other"]] = None
+ """Input source the synthesized gesture emulates.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ prevent_fling: Optional[bool] = None
+ """Whether fling was suppressed."""
+
+ repeat_count: Optional[int] = None
+ """Number of additional repeats of the scroll."""
+
+ repeat_delay_ms: Optional[int] = None
+ """Delay between repeats, in milliseconds."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ speed: Optional[int] = None
+ """Swipe speed in pixels per second."""
+
+ x: Optional[float] = None
+ """Viewport x coordinate in CSS pixels."""
+
+ x_distance: Optional[float] = None
+ """Horizontal scroll distance in CSS pixels; positive scrolls left."""
+
+ x_overscroll: Optional[float] = None
+ """Additional horizontal distance scrolled past the end."""
+
+ y: Optional[float] = None
+ """Viewport y coordinate in CSS pixels."""
+
+ y_distance: Optional[float] = None
+ """Vertical scroll distance in CSS pixels; positive scrolls up."""
+
+ y_overscroll: Optional[float] = None
+ """Additional vertical distance scrolled past the end."""
+
+
+class DataBrowserCdpInputSynthesizeTapGestureCommandData(BaseModel):
+ """Sanitized `Input.synthesizeTapGesture` arguments.
+
+ Canonical input: `Input.synthesizeTapGesture` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Input.synthesizeTapGesture"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ duration: Optional[int] = None
+ """Duration between touchdown and touchup, in milliseconds."""
+
+ gesture_source_type: Optional[Literal["default", "touch", "mouse", "other"]] = None
+ """Input source the synthesized gesture emulates.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ tap_count: Optional[int] = None
+ """Number of times to tap (2 is a double tap)."""
+
+ x: Optional[float] = None
+ """Viewport x coordinate in CSS pixels."""
+
+ y: Optional[float] = None
+ """Viewport y coordinate in CSS pixels."""
+
+
+class DataBrowserCdpDomSetFileInputFilesCommandData(BaseModel):
+ """Sanitized `DOM.setFileInputFiles` arguments.
+
+ Canonical input: `DOM.setFileInputFiles` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ file_count: int
+ """Number of files handed to the input. File paths are never captured."""
+
+ method: Literal["DOM.setFileInputFiles"]
+
+ backend_node_id: Optional[int] = None
+ """Opaque backend DOM node identifier the command targeted."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ node_id: Optional[int] = None
+ """Opaque DOM node identifier the command targeted."""
+
+ object_id: Optional[str] = None
+ """Opaque Runtime remote object identifier the command targeted.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpDomFocusCommandData(BaseModel):
+ """Sanitized `DOM.focus` arguments.
+
+ Canonical input: `DOM.focus` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["DOM.focus"]
+
+ backend_node_id: Optional[int] = None
+ """Opaque backend DOM node identifier the command targeted."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ node_id: Optional[int] = None
+ """Opaque DOM node identifier the command targeted."""
+
+ object_id: Optional[str] = None
+ """Opaque Runtime remote object identifier the command targeted.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpDomScrollIntoViewIfNeededCommandData(BaseModel):
+ """Sanitized `DOM.scrollIntoViewIfNeeded` arguments.
+
+ Canonical input: `DOM.scrollIntoViewIfNeeded` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["DOM.scrollIntoViewIfNeeded"]
+
+ backend_node_id: Optional[int] = None
+ """Opaque backend DOM node identifier the command targeted."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ node_id: Optional[int] = None
+ """Opaque DOM node identifier the command targeted."""
+
+ object_id: Optional[str] = None
+ """Opaque Runtime remote object identifier the command targeted.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ rect_height: Optional[float] = None
+ """Height of the rect the command scrolled to."""
+
+ rect_width: Optional[float] = None
+ """Width of the rect the command scrolled to."""
+
+ rect_x: Optional[float] = None
+ """X offset of the rect the command scrolled to, relative to the node."""
+
+ rect_y: Optional[float] = None
+ """Y offset of the rect the command scrolled to, relative to the node."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageBringToFrontCommandData(BaseModel):
+ """Sanitized `Page.bringToFront` arguments.
+
+ Canonical input: `Page.bringToFront` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.bringToFront"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageCaptureScreenshotCommandData(BaseModel):
+ """Sanitized `Page.captureScreenshot` arguments.
+
+ Canonical input: `Page.captureScreenshot` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.captureScreenshot"]
+
+ capture_beyond_viewport: Optional[bool] = None
+ """Whether the capture extended past the viewport."""
+
+ clip_height: Optional[float] = None
+ """Clip region height in CSS pixels."""
+
+ clip_scale: Optional[float] = None
+ """Clip region page scale factor."""
+
+ clip_width: Optional[float] = None
+ """Clip region width in CSS pixels."""
+
+ clip_x: Optional[float] = None
+ """Clip region x offset in CSS pixels."""
+
+ clip_y: Optional[float] = None
+ """Clip region y offset in CSS pixels."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ format: Optional[Literal["jpeg", "png", "webp", "other"]] = None
+ """Image format requested (`jpeg`, `png` or `webp`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ from_surface: Optional[bool] = None
+ """Whether the capture was taken from the surface rather than the view."""
+
+ optimize_for_speed: Optional[bool] = None
+ """Whether encoding favored speed over size."""
+
+ quality: Optional[int] = None
+ """Compression quality, 0 to 100, for lossy formats."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageCaptureSnapshotCommandData(BaseModel):
+ """Sanitized `Page.captureSnapshot` arguments.
+
+ Canonical input: `Page.captureSnapshot` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.captureSnapshot"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ format: Optional[Literal["mhtml", "other"]] = None
+ """Snapshot format requested (`mhtml`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageHandleJavaScriptDialogCommandData(BaseModel):
+ """Sanitized `Page.handleJavaScriptDialog` arguments.
+
+ Canonical input: `Page.handleJavaScriptDialog` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ accept: bool
+ """Whether the dialog was accepted or dismissed."""
+
+ method: Literal["Page.handleJavaScriptDialog"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ prompt_text_length: Optional[int] = None
+ """Number of characters entered into a prompt dialog.
+
+ The text itself is never captured.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageNavigateCommandData(BaseModel):
+ """Sanitized `Page.navigate` arguments.
+
+ Canonical input: `Page.navigate` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.navigate"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ frame_id: Optional[str] = None
+ """Opaque frame identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ referrer_policy: Optional[
+ Literal[
+ "noReferrer",
+ "noReferrerWhenDowngrade",
+ "origin",
+ "originWhenCrossOrigin",
+ "sameOrigin",
+ "strictOrigin",
+ "strictOriginWhenCrossOrigin",
+ "unsafeUrl",
+ "other",
+ ]
+ ] = None
+ """Referrer policy named by the command.
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ referrer_present: Optional[bool] = None
+ """Whether the command carried a referrer. The referrer itself is never captured."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ transition_type: Optional[
+ Literal[
+ "link",
+ "typed",
+ "address_bar",
+ "auto_bookmark",
+ "auto_subframe",
+ "manual_subframe",
+ "generated",
+ "auto_toplevel",
+ "form_submit",
+ "reload",
+ "keyword",
+ "keyword_generated",
+ "other",
+ ]
+ ] = None
+ """Navigation reason reported by the caller (e.g.
+
+ `link`, `typed`, `reload`). A value the protocol does not define is reported as
+ `other`.
+ """
+
+ url_scheme: Optional[str] = None
+ """Scheme of the destination URL (e.g.
+
+ `https`, `about`, `data`). The rest of the URL is never captured.
+ """
+
+
+class DataBrowserCdpPageNavigateToHistoryEntryCommandData(BaseModel):
+ """Sanitized `Page.navigateToHistoryEntry` arguments.
+
+ Canonical input: `Page.navigateToHistoryEntry` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ entry_id: int
+ """History entry the command navigated to."""
+
+ method: Literal["Page.navigateToHistoryEntry"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageReloadCommandData(BaseModel):
+ """Sanitized `Page.reload` arguments.
+
+ Canonical input: `Page.reload` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.reload"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ ignore_cache: Optional[bool] = None
+ """Whether the reload bypassed the cache."""
+
+ loader_id: Optional[str] = None
+ """Opaque document loader identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ script_length: Optional[int] = None
+ """Number of characters in the injected script."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPagePrintToPdfCommandData(BaseModel):
+ """Sanitized `Page.printToPDF` arguments.
+
+ Canonical input: `Page.printToPDF` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.printToPDF"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ display_header_footer: Optional[bool] = None
+ """Whether a header and footer were rendered."""
+
+ footer_template_present: Optional[bool] = None
+ """Whether a footer template was supplied. The template itself is never captured."""
+
+ generate_document_outline: Optional[bool] = None
+ """Whether a document outline was embedded."""
+
+ generate_tagged_pdf: Optional[bool] = None
+ """Whether a tagged (accessible) PDF was requested."""
+
+ header_template_present: Optional[bool] = None
+ """Whether a header template was supplied. The template itself is never captured."""
+
+ landscape: Optional[bool] = None
+ """Whether the page was laid out in landscape."""
+
+ margin_bottom: Optional[float] = None
+ """Bottom margin in inches."""
+
+ margin_left: Optional[float] = None
+ """Left margin in inches."""
+
+ margin_right: Optional[float] = None
+ """Right margin in inches."""
+
+ margin_top: Optional[float] = None
+ """Top margin in inches."""
+
+ page_ranges_present: Optional[bool] = None
+ """Whether a page range was supplied."""
+
+ paper_height: Optional[float] = None
+ """Paper height in inches."""
+
+ paper_width: Optional[float] = None
+ """Paper width in inches."""
+
+ prefer_css_page_size: Optional[bool] = None
+ """Whether the CSS page size was preferred over the paper size."""
+
+ print_background: Optional[bool] = None
+ """Whether background graphics were printed."""
+
+ scale: Optional[float] = None
+ """Page render scale."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ transfer_mode: Optional[Literal["ReturnAsBase64", "ReturnAsStream", "other"]] = None
+ """How the PDF was returned (`ReturnAsBase64` or `ReturnAsStream`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+
+class DataBrowserCdpPageStartScreencastCommandData(BaseModel):
+ """Sanitized `Page.startScreencast` arguments.
+
+ Canonical input: `Page.startScreencast` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.startScreencast"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ every_nth_frame: Optional[int] = None
+ """Frame sampling interval."""
+
+ format: Optional[Literal["jpeg", "png", "other"]] = None
+ """Frame format requested (`jpeg` or `png`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ max_height: Optional[int] = None
+ """Maximum frame height in pixels."""
+
+ max_width: Optional[int] = None
+ """Maximum frame width in pixels."""
+
+ quality: Optional[int] = None
+ """Compression quality, 0 to 100."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageStopScreencastCommandData(BaseModel):
+ """Sanitized `Page.stopScreencast` arguments.
+
+ Canonical input: `Page.stopScreencast` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.stopScreencast"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageStopLoadingCommandData(BaseModel):
+ """Sanitized `Page.stopLoading` arguments.
+
+ Canonical input: `Page.stopLoading` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.stopLoading"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageCloseCommandData(BaseModel):
+ """Sanitized `Page.close` arguments.
+
+ Canonical input: `Page.close` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.close"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpPageSetWebLifecycleStateCommandData(BaseModel):
+ """Sanitized `Page.setWebLifecycleState` arguments.
+
+ Canonical input: `Page.setWebLifecycleState` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Page.setWebLifecycleState"]
+
+ state: Literal["frozen", "active", "other"]
+ """Lifecycle state applied (`frozen` or `active`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpTargetActivateTargetCommandData(BaseModel):
+ """Sanitized `Target.activateTarget` arguments.
+
+ Canonical input: `Target.activateTarget` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Target.activateTarget"]
+
+ target_id: str
+ """Opaque target identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpTargetCloseTargetCommandData(BaseModel):
+ """Sanitized `Target.closeTarget` arguments.
+
+ Canonical input: `Target.closeTarget` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Target.closeTarget"]
+
+ target_id: str
+ """Opaque target identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpTargetCreateTargetCommandData(BaseModel):
+ """Sanitized `Target.createTarget` arguments.
+
+ Canonical input: `Target.createTarget` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Target.createTarget"]
+
+ background: Optional[bool] = None
+ """Whether the target was created in the background."""
+
+ browser_context_id: Optional[str] = None
+ """Opaque browser context identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ enable_begin_frame_control: Optional[bool] = None
+ """Whether BeginFrame control was enabled (headless only)."""
+
+ focus: Optional[bool] = None
+ """Whether the new target was focused."""
+
+ for_tab: Optional[bool] = None
+ """Whether a tab target rather than a page target was created."""
+
+ height: Optional[int] = None
+ """Window height in DIP."""
+
+ hidden: Optional[bool] = None
+ """Whether the target was created hidden."""
+
+ left: Optional[int] = None
+ """Window x position in screen coordinates."""
+
+ new_window: Optional[bool] = None
+ """Whether a new window was requested."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ top: Optional[int] = None
+ """Window y position in screen coordinates."""
+
+ url_scheme: Optional[str] = None
+ """Scheme of the destination URL (e.g.
+
+ `https`, `about`, `data`). The rest of the URL is never captured.
+ """
+
+ width: Optional[int] = None
+ """Window width in DIP."""
+
+ window_state: Optional[Literal["normal", "minimized", "maximized", "fullscreen", "other"]] = None
+ """Window state requested (`normal`, `minimized`, `maximized`, `fullscreen`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+
+class DataBrowserCdpTargetCreateBrowserContextCommandData(BaseModel):
+ """Sanitized `Target.createBrowserContext` arguments.
+
+ Canonical input: `Target.createBrowserContext` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Target.createBrowserContext"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ dispose_on_detach: Optional[bool] = None
+ """Whether the context is disposed when the debugging session detaches."""
+
+ proxy_bypass_list_present: Optional[bool] = None
+ """Whether a proxy bypass list was configured."""
+
+ proxy_server_present: Optional[bool] = None
+ """Whether a proxy was configured. The proxy address is never captured."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ universal_network_access_origin_count: Optional[int] = None
+ """Number of origins granted universal network access.
+
+ The origins themselves are never captured.
+ """
+
+
+class DataBrowserCdpTargetDisposeBrowserContextCommandData(BaseModel):
+ """Sanitized `Target.disposeBrowserContext` arguments.
+
+ Canonical input: `Target.disposeBrowserContext` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ browser_context_id: str
+ """Opaque browser context identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ method: Literal["Target.disposeBrowserContext"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpTargetOpenDevToolsCommandData(BaseModel):
+ """Sanitized `Target.openDevTools` arguments.
+
+ Canonical input: `Target.openDevTools` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Target.openDevTools"]
+
+ target_id: str
+ """Opaque target identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ panel_id: Optional[str] = None
+ """DevTools panel opened.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpBrowserCancelDownloadCommandData(BaseModel):
+ """Sanitized `Browser.cancelDownload` arguments.
+
+ Canonical input: `Browser.cancelDownload` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ download_guid: str
+ """Opaque identifier of the download that was cancelled.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ method: Literal["Browser.cancelDownload"]
+
+ browser_context_id: Optional[str] = None
+ """Opaque browser context identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpBrowserCloseCommandData(BaseModel):
+ """Sanitized `Browser.close` arguments.
+
+ Canonical input: `Browser.close` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Browser.close"]
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+class DataBrowserCdpBrowserSetWindowBoundsCommandData(BaseModel):
+ """Sanitized `Browser.setWindowBounds` arguments.
+
+ Canonical input: `Browser.setWindowBounds` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Browser.setWindowBounds"]
+
+ window_id: int
+ """Browser window identifier."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ height: Optional[int] = None
+ """Window height in DIP."""
+
+ left: Optional[int] = None
+ """Window x position in screen coordinates."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ top: Optional[int] = None
+ """Window y position in screen coordinates."""
+
+ width: Optional[int] = None
+ """Window width in DIP."""
+
+ window_state: Optional[Literal["normal", "minimized", "maximized", "fullscreen", "other"]] = None
+ """Window state requested (`normal`, `minimized`, `maximized`, `fullscreen`).
+
+ A value the protocol does not define is reported as `other`.
+ """
+
+
+class DataBrowserCdpBrowserSetContentsSizeCommandData(BaseModel):
+ """Sanitized `Browser.setContentsSize` arguments.
+
+ Canonical input: `Browser.setContentsSize` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ method: Literal["Browser.setContentsSize"]
+
+ window_id: int
+ """Browser window identifier."""
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ height: Optional[int] = None
+ """Contents height in DIP."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+ width: Optional[int] = None
+ """Contents width in DIP."""
+
+
+class DataBrowserCdpAutofillTriggerCommandData(BaseModel):
+ """Sanitized `Autofill.trigger` arguments.
+
+ Canonical input: `Autofill.trigger` in devtools-protocol@2d019e73, pinned at https://github.com/ChromeDevTools/devtools-protocol/blob/2d019e73eb371d1d6985d26d395d78bd8f8a22ba/json/browser_protocol.json. Every argument of this command has a retained or redacted decision in lib/devtoolsproxy/testdata/cdp_arguments.yaml.
+ """
+
+ field_id: int
+ """Opaque backend node identifier of the field that was autofilled."""
+
+ method: Literal["Autofill.trigger"]
+
+ address_field_count: Optional[int] = None
+ """Number of address fields the command filled.
+
+ Their names and values are never captured.
+ """
+
+ command_id: Optional[int] = None
+ """
+ The command's JSON-RPC id, so the command can be joined to the result the
+ browser returned for it. Absent when the client sent none.
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies the CDP proxy connection the command arrived on, matching
+ `cdp_connect` and `cdp_disconnect`. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ frame_id: Optional[str] = None
+ """Opaque frame identifier.
+
+ Clipped to 128 characters; a longer value is not a real identifier.
+ """
+
+ mode: Optional[Literal["card", "address"]] = None
+ """What was filled: `card` or `address`. The values themselves are never captured."""
+
+ session_id: Optional[str] = None
+ """CDP session identifier the command was addressed to.
+
+ Absent for browser-level commands. Clipped to 128 characters.
+ """
+
+
+Data: TypeAlias = Annotated[
+ Union[
+ DataBrowserCdpInputDispatchMouseEventCommandData,
+ DataBrowserCdpInputDispatchKeyEventCommandData,
+ DataBrowserCdpInputInsertTextCommandData,
+ DataBrowserCdpInputImeSetCompositionCommandData,
+ DataBrowserCdpInputDispatchTouchEventCommandData,
+ DataBrowserCdpInputDispatchDragEventCommandData,
+ DataBrowserCdpInputCancelDraggingCommandData,
+ DataBrowserCdpInputEmulateTouchFromMouseEventCommandData,
+ DataBrowserCdpInputSynthesizePinchGestureCommandData,
+ DataBrowserCdpInputSynthesizeScrollGestureCommandData,
+ DataBrowserCdpInputSynthesizeTapGestureCommandData,
+ DataBrowserCdpDomSetFileInputFilesCommandData,
+ DataBrowserCdpDomFocusCommandData,
+ DataBrowserCdpDomScrollIntoViewIfNeededCommandData,
+ DataBrowserCdpPageBringToFrontCommandData,
+ DataBrowserCdpPageCaptureScreenshotCommandData,
+ DataBrowserCdpPageCaptureSnapshotCommandData,
+ DataBrowserCdpPageHandleJavaScriptDialogCommandData,
+ DataBrowserCdpPageNavigateCommandData,
+ DataBrowserCdpPageNavigateToHistoryEntryCommandData,
+ DataBrowserCdpPageReloadCommandData,
+ DataBrowserCdpPagePrintToPdfCommandData,
+ DataBrowserCdpPageStartScreencastCommandData,
+ DataBrowserCdpPageStopScreencastCommandData,
+ DataBrowserCdpPageStopLoadingCommandData,
+ DataBrowserCdpPageCloseCommandData,
+ DataBrowserCdpPageSetWebLifecycleStateCommandData,
+ DataBrowserCdpTargetActivateTargetCommandData,
+ DataBrowserCdpTargetCloseTargetCommandData,
+ DataBrowserCdpTargetCreateTargetCommandData,
+ DataBrowserCdpTargetCreateBrowserContextCommandData,
+ DataBrowserCdpTargetDisposeBrowserContextCommandData,
+ DataBrowserCdpTargetOpenDevToolsCommandData,
+ DataBrowserCdpBrowserCancelDownloadCommandData,
+ DataBrowserCdpBrowserCloseCommandData,
+ DataBrowserCdpBrowserSetWindowBoundsCommandData,
+ DataBrowserCdpBrowserSetContentsSizeCommandData,
+ DataBrowserCdpAutofillTriggerCommandData,
+ ],
+ PropertyInfo(discriminator="method"),
+]
+
+
+class BrowserCdpCommandEvent(BaseModel):
+ """
+ A browser-control command a client sent over the CDP WebSocket proxy: input gestures, navigation, dialog handling, file selection and screenshots. Configuration commands and the DOM/Runtime traffic a client library issues on the caller's behalf are not reported.
+ One event per browser-control command that reached the browser. The command stream is not sampled, coalesced or reordered. An event is lost only when the method is excluded by telemetry configuration, when the command's arguments do not decode, or when classification cannot keep up. Exclusions are counted in `cdp_disconnect.telemetry_excluded`; the rest in `cdp_disconnect.telemetry_dropped`.
+ """
+
+ category: Literal["control"]
+
+ data: Data
+ """Per-command payload for `cdp_command` events, discriminated by `method`.
+
+ Each variant carries only the arguments approved for that command: values that
+ could hold a secret — typed and composition text, URLs, referrers, scripts,
+ templates, file paths, drag contents and autofill values — are replaced by a
+ length, a count, a presence flag, an enum or a URL scheme and host.
+ """
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["cdp_command"]
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_cdp_command_method.py b/src/kernel/types/browsers/browser_cdp_command_method.py
new file mode 100644
index 00000000..18ccbb96
--- /dev/null
+++ b/src/kernel/types/browsers/browser_cdp_command_method.py
@@ -0,0 +1,46 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["BrowserCdpCommandMethod"]
+
+BrowserCdpCommandMethod: TypeAlias = Literal[
+ "Input.dispatchMouseEvent",
+ "Input.dispatchKeyEvent",
+ "Input.insertText",
+ "Input.imeSetComposition",
+ "Input.dispatchTouchEvent",
+ "Input.dispatchDragEvent",
+ "Input.cancelDragging",
+ "Input.emulateTouchFromMouseEvent",
+ "Input.synthesizePinchGesture",
+ "Input.synthesizeScrollGesture",
+ "Input.synthesizeTapGesture",
+ "DOM.setFileInputFiles",
+ "DOM.focus",
+ "DOM.scrollIntoViewIfNeeded",
+ "Page.bringToFront",
+ "Page.captureScreenshot",
+ "Page.captureSnapshot",
+ "Page.handleJavaScriptDialog",
+ "Page.navigate",
+ "Page.navigateToHistoryEntry",
+ "Page.reload",
+ "Page.printToPDF",
+ "Page.startScreencast",
+ "Page.stopScreencast",
+ "Page.stopLoading",
+ "Page.close",
+ "Page.setWebLifecycleState",
+ "Target.activateTarget",
+ "Target.closeTarget",
+ "Target.createTarget",
+ "Target.createBrowserContext",
+ "Target.disposeBrowserContext",
+ "Target.openDevTools",
+ "Browser.cancelDownload",
+ "Browser.close",
+ "Browser.setWindowBounds",
+ "Browser.setContentsSize",
+ "Autofill.trigger",
+]
diff --git a/src/kernel/types/browsers/browser_cdp_connect_event.py b/src/kernel/types/browsers/browser_cdp_connect_event.py
new file mode 100644
index 00000000..042ab131
--- /dev/null
+++ b/src/kernel/types/browsers/browser_cdp_connect_event.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserCdpConnectEvent", "Data"]
+
+
+class Data(BaseModel):
+ connection_id: Optional[str] = None
+ """
+ Identifies this CDP proxy connection, matching the connection_id on the
+ cdp_command events that arrived on it. Two clients driving the same browser are
+ told apart by this.
+ """
+
+
+class BrowserCdpConnectEvent(BaseModel):
+ """An external client (e.g.
+
+ customer SDK, Playwright, Puppeteer) connected to the CDP WebSocket proxy on this VM.
+ """
+
+ category: Literal["connection"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["cdp_connect"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_cdp_disconnect_event.py b/src/kernel/types/browsers/browser_cdp_disconnect_event.py
new file mode 100644
index 00000000..5ec69172
--- /dev/null
+++ b/src/kernel/types/browsers/browser_cdp_disconnect_event.py
@@ -0,0 +1,73 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserCdpDisconnectEvent", "Data"]
+
+
+class Data(BaseModel):
+ duration_ms: float
+ """Wall-clock duration of the connection in milliseconds."""
+
+ message_count: int
+ """Number of CDP messages relayed across the connection in either direction."""
+
+ reason: Literal["client_close", "upstream_changed", "upstream_error", "context_cancelled"]
+ """Why the connection ended.
+
+ client_close: the client initiated the close. upstream_changed: Chromium
+ restarted mid-session and the proxy tore down so the client could reconnect
+ against the new upstream. upstream_error: upstream dial or message pump errored.
+ context_cancelled: the request context was cancelled (typically server
+ shutdown).
+ """
+
+ connection_id: Optional[str] = None
+ """
+ Identifies this CDP proxy connection, matching the connection_id on the
+ cdp_command events that arrived on it. Two clients driving the same browser are
+ told apart by this.
+ """
+
+ telemetry_dropped: Optional[int] = None
+ """
+ Number of forwarded client frames the classifier never saw, because it could not
+ keep up or because classification failed. An upper bound on lost commands rather
+ than a count: a saturated queue turns away whatever arrives next, which may be
+ library traffic that would have produced no event. Telemetry loss only; every
+ command was still relayed to the browser. Absent on events from a browser image
+ predating the field, which is not the same as zero.
+ """
+
+ telemetry_excluded: Optional[int] = None
+ """
+ Number of forwarded client commands that produced no cdp_command event because
+ their method is listed in control.cdp.excluded_methods. Configuration rather
+ than loss, so it is counted apart from telemetry_dropped.
+ """
+
+
+class BrowserCdpDisconnectEvent(BaseModel):
+ """An external client disconnected from the CDP WebSocket proxy on this VM.
+
+ Pair with the immediately preceding cdp_connect on the same stream.
+ """
+
+ category: Literal["connection"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["cdp_disconnect"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_console_error_event.py b/src/kernel/types/browsers/browser_console_error_event.py
new file mode 100644
index 00000000..ef1c8949
--- /dev/null
+++ b/src/kernel/types/browsers/browser_console_error_event.py
@@ -0,0 +1,94 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserConsoleErrorEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ text: str
+ """Human-readable error text, as the browser console would display it.
+
+ For console.error() calls, the first argument coerced to a string. For uncaught
+ exceptions, the prefix and error message, e.g. "Uncaught Error: boom" or
+ "Uncaught (in promise) TypeError: x is not a function".
+ """
+
+ args: Optional[List[str]] = None
+ """All console arguments coerced to strings.
+
+ Present only when sourced from Runtime.consoleAPICalled.
+ """
+
+ column: Optional[int] = None
+ """Column number in the script where the exception was thrown.
+
+ Present only when sourced from Runtime.exceptionThrown.
+ """
+
+ level: Optional[str] = None
+ """CDP console type value, always "error".
+
+ Present only when sourced from Runtime.consoleAPICalled.
+ """
+
+ line: Optional[int] = None
+ """Line number in the script where the exception was thrown.
+
+ Present only when sourced from Runtime.exceptionThrown.
+ """
+
+ source_url: Optional[str] = None
+ """URL of the script file that threw the exception.
+
+ Present only when sourced from Runtime.exceptionThrown.
+ """
+
+ stack_trace: Optional["BrowserCallStack"] = None
+ """
+ CDP Runtime.StackTrace representing the JavaScript call stack at the time of an
+ event. Fields use CDP naming conventions rather than snake_case to match the
+ Chrome DevTools Protocol wire format.
+ """
+
+
+class BrowserConsoleErrorEvent(BaseModel):
+ """A browser console error or uncaught JavaScript exception event.
+
+ Emitted from two distinct CDP sources with different data shapes. Runtime.consoleAPICalled (console.error calls) produces level, text, args, and stack_trace. Runtime.exceptionThrown (uncaught exceptions) produces text, line, column, source_url, and stack_trace. Fields not applicable to the source are absent.
+ """
+
+ category: Literal["console"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["console_error"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
+
+
+from .browser_call_stack import BrowserCallStack
diff --git a/src/kernel/types/browsers/browser_console_log_event.py b/src/kernel/types/browsers/browser_console_log_event.py
new file mode 100644
index 00000000..338f6c85
--- /dev/null
+++ b/src/kernel/types/browsers/browser_console_log_event.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserConsoleLogEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ args: Optional[List[str]] = None
+ """All console arguments coerced to strings."""
+
+ level: Optional[str] = None
+ """CDP Runtime.consoleAPICalled type, passed through unfiltered from Chrome.
+
+ error is routed to console_error events instead; all other CDP console types
+ appear here. See CDP spec for the full enum.
+ """
+
+ stack_trace: Optional["BrowserCallStack"] = None
+ """
+ CDP Runtime.StackTrace representing the JavaScript call stack at the time of an
+ event. Fields use CDP naming conventions rather than snake_case to match the
+ Chrome DevTools Protocol wire format.
+ """
+
+ text: Optional[str] = None
+ """First console argument coerced to string."""
+
+
+class BrowserConsoleLogEvent(BaseModel):
+ """A browser console log event (console.log, console.info, console.warn, etc.)."""
+
+ category: Literal["console"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["console_log"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
+
+
+from .browser_call_stack import BrowserCallStack
diff --git a/src/kernel/types/browsers/browser_event_context.py b/src/kernel/types/browsers/browser_event_context.py
new file mode 100644
index 00000000..1342799e
--- /dev/null
+++ b/src/kernel/types/browsers/browser_event_context.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["BrowserEventContext"]
+
+
+class BrowserEventContext(BaseModel):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ frame_id: Optional[str] = None
+ """CDP frame identifier within the target."""
+
+ loader_id: Optional[str] = None
+ """CDP document loader identifier, reset on each navigation."""
+
+ nav_seq: Optional[int] = None
+ """
+ Monotonically increasing navigation sequence number, incremented on each
+ top-level navigation within the target.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier for the target connection."""
+
+ target_id: Optional[str] = None
+ """Browser target identifier (stable across navigations within a tab)."""
+
+ target_type: Optional[
+ Literal["page", "iframe", "worker", "background_page", "service_worker", "shared_worker", "other"]
+ ] = None
+ """CDP target type of the page that produced the event."""
+
+ url: Optional[str] = None
+ """
+ URL relevant to this event — page URL for navigation and page events, request
+ URL for network events.
+ """
diff --git a/src/kernel/types/browsers/browser_event_source.py b/src/kernel/types/browsers/browser_event_source.py
new file mode 100644
index 00000000..40125b6b
--- /dev/null
+++ b/src/kernel/types/browsers/browser_event_source.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["BrowserEventSource"]
+
+
+class BrowserEventSource(BaseModel):
+ """Provenance metadata identifying which producer emitted the event."""
+
+ kind: Literal["cdp", "kernel_api", "extension", "local_process"]
+ """Event producer.
+
+ cdp: Chrome DevTools Protocol events from the browser. kernel_api: Kernel API
+ server. extension: injected Chrome extension. local_process: system process
+ running alongside the browser.
+ """
+
+ event: Optional[str] = None
+ """Producer-specific event name (e.g.
+
+ Runtime.consoleAPICalled for CDP-sourced console events, Runtime.exceptionThrown
+ for uncaught exceptions).
+ """
+
+ metadata: Optional[Dict[str, str]] = None
+ """Producer-specific context (e.g. CDP target/session/frame IDs)."""
diff --git a/src/kernel/types/browsers/browser_http_headers.py b/src/kernel/types/browsers/browser_http_headers.py
new file mode 100644
index 00000000..b55ae855
--- /dev/null
+++ b/src/kernel/types/browsers/browser_http_headers.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["BrowserHTTPHeaders"]
+
+BrowserHTTPHeaders: TypeAlias = Dict[str, object]
diff --git a/src/kernel/types/browsers/browser_interaction_click_event.py b/src/kernel/types/browsers/browser_interaction_click_event.py
new file mode 100644
index 00000000..7c4f6119
--- /dev/null
+++ b/src/kernel/types/browsers/browser_interaction_click_event.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserInteractionClickEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ selector: Optional[str] = None
+ """CSS selector path to the clicked element."""
+
+ tag: Optional[str] = None
+ """HTML tag name of the clicked element in uppercase (e.g. BUTTON, A, DIV)."""
+
+ text: Optional[str] = None
+ """Visible text content of the clicked element, trimmed."""
+
+ x: Optional[int] = None
+ """Viewport x-coordinate of the click in CSS pixels."""
+
+ y: Optional[int] = None
+ """Viewport y-coordinate of the click in CSS pixels."""
+
+
+class BrowserInteractionClickEvent(BaseModel):
+ """A browser user click event captured via injected page script."""
+
+ category: Literal["interaction"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["interaction_click"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_interaction_key_event.py b/src/kernel/types/browsers/browser_interaction_key_event.py
new file mode 100644
index 00000000..93f2c43e
--- /dev/null
+++ b/src/kernel/types/browsers/browser_interaction_key_event.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserInteractionKeyEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ key: Optional[str] = None
+ """Key value from the KeyboardEvent (e.g. Enter, Backspace, a)."""
+
+ selector: Optional[str] = None
+ """CSS selector path to the element that had focus when the key was pressed."""
+
+ tag: Optional[str] = None
+ """HTML tag name of the focused element in uppercase (e.g. INPUT, TEXTAREA, DIV)."""
+
+
+class BrowserInteractionKeyEvent(BaseModel):
+ """A browser keyboard event captured via injected page script."""
+
+ category: Literal["interaction"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["interaction_key"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_interaction_scroll_settled_event.py b/src/kernel/types/browsers/browser_interaction_scroll_settled_event.py
new file mode 100644
index 00000000..2c7ee24c
--- /dev/null
+++ b/src/kernel/types/browsers/browser_interaction_scroll_settled_event.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserInteractionScrollSettledEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ from_x: Optional[int] = None
+ """Scroll x-position at the start of the scroll gesture in CSS pixels."""
+
+ from_y: Optional[int] = None
+ """Scroll y-position at the start of the scroll gesture in CSS pixels."""
+
+ target_selector: Optional[str] = None
+ """CSS selector path to the scrolled element."""
+
+ to_x: Optional[int] = None
+ """Final scroll x-position after the gesture settled in CSS pixels."""
+
+ to_y: Optional[int] = None
+ """Final scroll y-position after the gesture settled in CSS pixels."""
+
+
+class BrowserInteractionScrollSettledEvent(BaseModel):
+ """
+ A browser scroll settled event emitted after scroll position stops changing, captured via injected page script.
+ """
+
+ category: Literal["interaction"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["interaction_scroll_settled"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_live_view_connect_event.py b/src/kernel/types/browsers/browser_live_view_connect_event.py
new file mode 100644
index 00000000..620e3d36
--- /dev/null
+++ b/src/kernel/types/browsers/browser_live_view_connect_event.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserLiveViewConnectEvent", "Data"]
+
+
+class Data(BaseModel):
+ session_id: str
+ """Live view session identifier.
+
+ Stable across reconnects, so a transient network blip can emit two events with
+ the same session_id.
+ """
+
+
+class BrowserLiveViewConnectEvent(BaseModel):
+ """A live view client connected to the headful browser's WebRTC server.
+
+ Headful only; not emitted for headless images.
+ """
+
+ category: Literal["connection"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["live_view_connect"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_live_view_disconnect_event.py b/src/kernel/types/browsers/browser_live_view_disconnect_event.py
new file mode 100644
index 00000000..a847d34c
--- /dev/null
+++ b/src/kernel/types/browsers/browser_live_view_disconnect_event.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserLiveViewDisconnectEvent", "Data"]
+
+
+class Data(BaseModel):
+ duration_ms: float
+ """Wall-clock duration of the connection in milliseconds."""
+
+ session_id: str
+ """
+ Live view session identifier; matches the corresponding live_view_connect event.
+ """
+
+
+class BrowserLiveViewDisconnectEvent(BaseModel):
+ """A live view client disconnected from the headful browser's WebRTC server.
+
+ Pair with live_view_connect by session_id.
+ """
+
+ category: Literal["connection"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["live_view_disconnect"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_monitor_disconnected_event.py b/src/kernel/types/browsers/browser_monitor_disconnected_event.py
new file mode 100644
index 00000000..de4afe5e
--- /dev/null
+++ b/src/kernel/types/browsers/browser_monitor_disconnected_event.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserMonitorDisconnectedEvent", "Data"]
+
+
+class Data(BaseModel):
+ reason: Optional[Literal["chrome_restarted"]] = None
+ """Reason for the disconnection. chrome_restarted: Chrome process restarted."""
+
+
+class BrowserMonitorDisconnectedEvent(BaseModel):
+ """The CDP connection to Chrome was lost.
+
+ Telemetry events may be dropped until monitor_reconnected arrives. In-progress computed state is discarded rather than paused, so computed events still pending for the current navigation (network_idle, page_layout_settled, page_navigation_settled) never fire. monitor_reconnected does not restore them. After reattachment a fresh state machine starts, so computed events can resume before the next navigation and carry empty navigation context until one occurs.
+ """
+
+ category: Literal["monitor"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["monitor_disconnected"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_monitor_init_failed_event.py b/src/kernel/types/browsers/browser_monitor_init_failed_event.py
new file mode 100644
index 00000000..c3a7cfdb
--- /dev/null
+++ b/src/kernel/types/browsers/browser_monitor_init_failed_event.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserMonitorInitFailedEvent", "Data"]
+
+
+class Data(BaseModel):
+ step: Optional[str] = None
+ """The CDP method or initialization step that failed (e.g. Target.setAutoAttach)."""
+
+
+class BrowserMonitorInitFailedEvent(BaseModel):
+ """The CDP session could not be initialized."""
+
+ category: Literal["monitor"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["monitor_init_failed"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_monitor_reconnect_failed_event.py b/src/kernel/types/browsers/browser_monitor_reconnect_failed_event.py
new file mode 100644
index 00000000..c71bf530
--- /dev/null
+++ b/src/kernel/types/browsers/browser_monitor_reconnect_failed_event.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserMonitorReconnectFailedEvent", "Data"]
+
+
+class Data(BaseModel):
+ reason: Optional[Literal["reconnect_exhausted"]] = None
+ """Reason for the reconnection failure.
+
+ reconnect_exhausted: all retry attempts were used up without successfully
+ restoring the CDP connection.
+ """
+
+
+class BrowserMonitorReconnectFailedEvent(BaseModel):
+ """
+ The CDP connection to Chrome could not be re-established after exhausting all reconnection attempts. No further telemetry events will arrive on this session.
+ """
+
+ category: Literal["monitor"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["monitor_reconnect_failed"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_monitor_reconnected_event.py b/src/kernel/types/browsers/browser_monitor_reconnected_event.py
new file mode 100644
index 00000000..c27340c6
--- /dev/null
+++ b/src/kernel/types/browsers/browser_monitor_reconnected_event.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserMonitorReconnectedEvent", "Data"]
+
+
+class Data(BaseModel):
+ reconnect_duration_ms: Optional[int] = None
+ """Wall-clock time in milliseconds taken to reconnect after the disconnection."""
+
+
+class BrowserMonitorReconnectedEvent(BaseModel):
+ """
+ The CDP connection to Chrome was successfully re-established after a disconnection. Events emitted during the gap are lost. Computed state is reset, so navigation and network tracking restart fresh from this point.
+ """
+
+ category: Literal["monitor"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["monitor_reconnected"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_monitor_screenshot_event.py b/src/kernel/types/browsers/browser_monitor_screenshot_event.py
new file mode 100644
index 00000000..02135b47
--- /dev/null
+++ b/src/kernel/types/browsers/browser_monitor_screenshot_event.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserMonitorScreenshotEvent", "Data"]
+
+
+class Data(BaseModel):
+ png: Optional[str] = None
+ """Base64-encoded PNG screenshot of the browser viewport."""
+
+
+class BrowserMonitorScreenshotEvent(BaseModel):
+ """A periodic screenshot of the browser viewport."""
+
+ category: Literal["screenshot"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["monitor_screenshot"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_network_idle_event.py b/src/kernel/types/browsers/browser_network_idle_event.py
new file mode 100644
index 00000000..b1877115
--- /dev/null
+++ b/src/kernel/types/browsers/browser_network_idle_event.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserNetworkIdleEvent"]
+
+
+class BrowserNetworkIdleEvent(BaseModel):
+ """
+ A browser network idle event emitted after a 500ms quiet period with no in-flight HTTP requests.
+ """
+
+ category: Literal["network"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["network_idle"]
+
+ data: Optional[BrowserEventContext] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_network_loading_failed_event.py b/src/kernel/types/browsers/browser_network_loading_failed_event.py
new file mode 100644
index 00000000..ac6b7961
--- /dev/null
+++ b/src/kernel/types/browsers/browser_network_loading_failed_event.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserNetworkLoadingFailedEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ canceled: Optional[bool] = None
+ """True if the request was canceled by the browser or page script."""
+
+ error_text: Optional[str] = None
+ """Network error description (e.g. net::ERR_CONNECTION_REFUSED)."""
+
+ request_id: Optional[str] = None
+ """CDP request identifier matching the originating network_request event."""
+
+ resource_type: Optional[str] = None
+ """CDP Network.ResourceType for the request, passed through as-is from Chrome.
+
+ Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media,
+ Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more.
+ """
+
+
+class BrowserNetworkLoadingFailedEvent(BaseModel):
+ """A browser network loading failed event.
+
+ If the request was already in flight when CDP attached (no prior network_request was emitted for it), url, frame_id, loader_id, and resource_type are absent; BrowserEventContext is partially populated in that case.
+ """
+
+ category: Literal["network"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["network_loading_failed"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_network_request_event.py b/src/kernel/types/browsers/browser_network_request_event.py
new file mode 100644
index 00000000..efea950b
--- /dev/null
+++ b/src/kernel/types/browsers/browser_network_request_event.py
@@ -0,0 +1,76 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_http_headers import BrowserHTTPHeaders
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserNetworkRequestEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ document_url: Optional[str] = None
+ """URL of the document that initiated the request."""
+
+ headers: Optional[BrowserHTTPHeaders] = None
+ """Request headers."""
+
+ initiator_type: Optional[str] = None
+ """
+ CDP Initiator.type indicating what caused the request, passed through as-is from
+ Chrome. Known values include script, parser, preload, and other.
+ """
+
+ is_redirect: Optional[bool] = None
+ """True if this request is the result of a redirect."""
+
+ method: Optional[str] = None
+ """HTTP method as sent on the wire (e.g. GET, POST)."""
+
+ post_data: Optional[str] = None
+ """Request body for POST/PUT requests, if available."""
+
+ redirect_url: Optional[str] = None
+ """Original URL before the redirect, present when is_redirect is true."""
+
+ request_id: Optional[str] = None
+ """CDP request identifier, unique within the session."""
+
+ resource_type: Optional[str] = None
+ """CDP Network.ResourceType for the request, passed through as-is from Chrome.
+
+ Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media,
+ Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more.
+ """
+
+
+class BrowserNetworkRequestEvent(BaseModel):
+ """A browser network request sent event."""
+
+ category: Literal["network"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["network_request"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_network_response_event.py b/src/kernel/types/browsers/browser_network_response_event.py
new file mode 100644
index 00000000..b39fefb8
--- /dev/null
+++ b/src/kernel/types/browsers/browser_network_response_event.py
@@ -0,0 +1,73 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_http_headers import BrowserHTTPHeaders
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserNetworkResponseEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ body: Optional[str] = None
+ """Truncated response body, present only for text MIME types."""
+
+ headers: Optional[BrowserHTTPHeaders] = None
+ """Response headers."""
+
+ method: Optional[str] = None
+ """HTTP method of the original request."""
+
+ mime_type: Optional[str] = None
+ """MIME type of the response (e.g. text/html, application/json)."""
+
+ request_id: Optional[str] = None
+ """CDP request identifier matching the originating network_request event."""
+
+ resource_type: Optional[str] = None
+ """CDP Network.ResourceType for the request, passed through as-is from Chrome.
+
+ Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media,
+ Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more.
+ """
+
+ status: Optional[int] = None
+ """HTTP response status code."""
+
+ status_text: Optional[str] = None
+ """HTTP response status text (e.g. OK, Not Found)."""
+
+
+class BrowserNetworkResponseEvent(BaseModel):
+ """A browser network response received event.
+
+ Fired after the response body is fully received, not when headers arrive.
+ """
+
+ category: Literal["network"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["network_response"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_crashed_event.py b/src/kernel/types/browsers/browser_page_crashed_event.py
new file mode 100644
index 00000000..86c31400
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_crashed_event.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserPageCrashedEvent", "Data"]
+
+
+class Data(BaseModel):
+ target_id: str
+ """CDP target identifier of the crashed page."""
+
+ target_type: Literal["page", "iframe", "worker", "background_page", "service_worker", "shared_worker", "other"]
+ """CDP target type of the page that produced the event."""
+
+ url: str
+ """URL the page was on when its renderer process crashed."""
+
+
+class BrowserPageCrashedEvent(BaseModel):
+ """
+ A page's renderer process crashed (an "Aw, Snap!" failure) while the browser process itself stayed alive. Reported on the crashed page's session, with the session and target ids on `source.metadata`. Captured only while the `page` category is enabled.
+ """
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_crashed"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_dom_content_loaded_event.py b/src/kernel/types/browsers/browser_page_dom_content_loaded_event.py
new file mode 100644
index 00000000..99393cce
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_dom_content_loaded_event.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserPageDomContentLoadedEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ cdp_timestamp: Optional[float] = None
+ """
+ Chrome monotonic clock value in seconds at which DOMContentLoaded fired,
+ relative to browser process start (not Unix epoch). Use ts for wall-clock time.
+ """
+
+
+class BrowserPageDomContentLoadedEvent(BaseModel):
+ """A browser DOMContentLoaded event (CDP Page.domContentEventFired)."""
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_dom_content_loaded"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_layout_settled_event.py b/src/kernel/types/browsers/browser_page_layout_settled_event.py
new file mode 100644
index 00000000..f0558f12
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_layout_settled_event.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserPageLayoutSettledEvent"]
+
+
+class BrowserPageLayoutSettledEvent(BaseModel):
+ """
+ A browser layout settled event emitted 1 second after page load with no intervening layout shifts, indicating visual stability. Each layout shift resets the 1-second timer.
+ """
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_layout_settled"]
+
+ data: Optional[BrowserEventContext] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_layout_shift_event.py b/src/kernel/types/browsers/browser_page_layout_shift_event.py
new file mode 100644
index 00000000..fb578ac8
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_layout_shift_event.py
@@ -0,0 +1,71 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserPageLayoutShiftEvent", "Data", "DataLayoutShiftDetails"]
+
+
+class DataLayoutShiftDetails(BaseModel):
+ """PerformanceLayoutShift attributes from the Performance Timeline entry."""
+
+ had_recent_input: Optional[bool] = None
+ """
+ True if the layout shift was preceded by user input within 500ms, excluding it
+ from CLS.
+ """
+
+ value: Optional[float] = None
+ """Layout shift score for this entry (contribution to CLS)."""
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ duration: Optional[float] = None
+ """
+ Duration of the layout shift entry in milliseconds (always 0 for layout shifts
+ per spec).
+ """
+
+ layout_shift_details: Optional[DataLayoutShiftDetails] = None
+ """PerformanceLayoutShift attributes from the Performance Timeline entry."""
+
+ source_frame_id: Optional[str] = None
+ """CDP frame identifier of the frame where the layout shift occurred."""
+
+ time: Optional[float] = None
+ """Performance Timeline timestamp of the layout shift in milliseconds."""
+
+
+class BrowserPageLayoutShiftEvent(BaseModel):
+ """
+ A browser cumulative layout shift (CLS) event from the Performance Timeline API.
+ """
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_layout_shift"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_lcp_event.py b/src/kernel/types/browsers/browser_page_lcp_event.py
new file mode 100644
index 00000000..0bcbddaa
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_lcp_event.py
@@ -0,0 +1,77 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserPageLcpEvent", "Data", "DataLcpDetails"]
+
+
+class DataLcpDetails(BaseModel):
+ """LargestContentfulPaint attributes from the Performance Timeline entry."""
+
+ element_id: Optional[str] = None
+ """id attribute of the LCP element, if present."""
+
+ load_time: Optional[float] = None
+ """Load time of the LCP element in milliseconds."""
+
+ node_id: Optional[int] = None
+ """CDP DOM node identifier of the LCP element."""
+
+ render_time: Optional[float] = None
+ """
+ Render time of the LCP element in milliseconds; 0 for cross-origin images
+ without Timing-Allow-Origin.
+ """
+
+ size: Optional[float] = None
+ """Visible area of the LCP element in pixels squared."""
+
+ url: Optional[str] = None
+ """URL of the LCP element for image or video elements."""
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ lcp_details: Optional[DataLcpDetails] = None
+ """LargestContentfulPaint attributes from the Performance Timeline entry."""
+
+ source_frame_id: Optional[str] = None
+ """CDP frame identifier of the frame where the LCP element was rendered."""
+
+ time: Optional[float] = None
+ """Performance Timeline timestamp of the LCP entry in milliseconds."""
+
+
+class BrowserPageLcpEvent(BaseModel):
+ """
+ A browser Largest Contentful Paint (LCP) event from the Performance Timeline API.
+ """
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_lcp"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_load_event.py b/src/kernel/types/browsers/browser_page_load_event.py
new file mode 100644
index 00000000..f27658f1
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_load_event.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserPageLoadEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ cdp_timestamp: Optional[float] = None
+ """
+ Chrome monotonic clock value in seconds at which the load event fired, relative
+ to browser process start (not Unix epoch). Use ts for wall-clock time.
+ """
+
+
+class BrowserPageLoadEvent(BaseModel):
+ """A browser page load event (CDP Page.loadEventFired)."""
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_load"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_navigation_event.py b/src/kernel/types/browsers/browser_page_navigation_event.py
new file mode 100644
index 00000000..b4504ef5
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_navigation_event.py
@@ -0,0 +1,59 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserPageNavigationEvent", "Data"]
+
+
+class Data(BaseModel):
+ frame_id: Optional[str] = None
+ """CDP frame identifier of the navigated frame."""
+
+ loader_id: Optional[str] = None
+ """New CDP document loader identifier assigned for this navigation."""
+
+ parent_frame_id: Optional[str] = None
+ """
+ Parent frame identifier for subframe navigations; absent for top-level
+ navigations.
+ """
+
+ session_id: Optional[str] = None
+ """CDP session identifier."""
+
+ target_id: Optional[str] = None
+ """Browser target identifier."""
+
+ target_type: Optional[
+ Literal["page", "iframe", "worker", "background_page", "service_worker", "shared_worker", "other"]
+ ] = None
+ """CDP target type of the page that produced the event."""
+
+ url: Optional[str] = None
+ """URL navigated to."""
+
+
+class BrowserPageNavigationEvent(BaseModel):
+ """A browser page navigation started event (CDP Page.frameNavigated).
+
+ Carries nav context fields inline but not nav_seq, as this event resets the navigation epoch.
+ """
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_navigation"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_navigation_settled_event.py b/src/kernel/types/browsers/browser_page_navigation_settled_event.py
new file mode 100644
index 00000000..6e5d31c2
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_navigation_settled_event.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserPageNavigationSettledEvent"]
+
+
+class BrowserPageNavigationSettledEvent(BaseModel):
+ """
+ Emitted when page_dom_content_loaded and page_layout_settled have both fired for the same navigation, indicating the page is loaded and visually stable. Independent of network_idle; a single pending request does not block it.
+ """
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_navigation_settled"]
+
+ data: Optional[BrowserEventContext] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_page_tab_opened_event.py b/src/kernel/types/browsers/browser_page_tab_opened_event.py
new file mode 100644
index 00000000..9fcac2b4
--- /dev/null
+++ b/src/kernel/types/browsers/browser_page_tab_opened_event.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserPageTabOpenedEvent", "Data"]
+
+
+class Data(BaseModel):
+ opener_id: Optional[str] = None
+ """Target identifier of the tab that opened this one, if any."""
+
+ target_id: Optional[str] = None
+ """CDP target identifier for the newly opened tab."""
+
+ target_type: Optional[
+ Literal["page", "iframe", "worker", "background_page", "service_worker", "shared_worker", "other"]
+ ] = None
+ """CDP target type of the page that produced the event."""
+
+ title: Optional[str] = None
+ """Initial page title of the new tab."""
+
+ url: Optional[str] = None
+ """Initial URL of the new tab."""
+
+
+class BrowserPageTabOpenedEvent(BaseModel):
+ """
+ A new browser tab or target was opened (CDP Target.attachedToTarget for page targets). Fires before a CDP session is attached to the new target, so session_id, frame_id, loader_id, and nav_seq are absent; this event does not compose BrowserEventContext. Consumers reading context fields generically should treat it as a special case.
+ """
+
+ category: Literal["page"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["page_tab_opened"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_platform_api_call_event.py b/src/kernel/types/browsers/browser_platform_api_call_event.py
new file mode 100644
index 00000000..f6e78b3b
--- /dev/null
+++ b/src/kernel/types/browsers/browser_platform_api_call_event.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserPlatformAPICallEvent", "Data"]
+
+
+class Data(BaseModel):
+ duration_ms: float
+ """Wall-clock duration of the handler in milliseconds."""
+
+ operation_id: str
+ """Matched route's operation, named as the in-VM API names its handler (e.g.
+
+ ProcessExec, StartRecording).
+ """
+
+ request_id: str
+ """Per-request identifier from the in-VM API request middleware."""
+
+ status: int
+ """HTTP response status code."""
+
+
+class BrowserPlatformAPICallEvent(BaseModel):
+ """
+ An HTTP call that manages the browser VM rather than driving the browser, handled by the in-VM API server — recording lifecycle, filesystem and process management, telemetry and browser configuration. Mostly platform-induced (e.g. profile save, replay capture) rather than agent actions.
+ """
+
+ category: Literal["platform"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["platform_api_call"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_proxy_error_event.py b/src/kernel/types/browsers/browser_proxy_error_event.py
new file mode 100644
index 00000000..574f525d
--- /dev/null
+++ b/src/kernel/types/browsers/browser_proxy_error_event.py
@@ -0,0 +1,94 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+from .browser_event_context import BrowserEventContext
+
+__all__ = ["BrowserProxyErrorEvent", "Data"]
+
+
+class Data(BrowserEventContext):
+ """Browser event context stamped by the browser monitor onto all CDP-sourced events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ code: Literal[
+ "destination_blocked",
+ "destination_route_unavailable",
+ "network_policy_denied",
+ "provider_blacklisted",
+ "provider_unreachable",
+ "provider_rejected",
+ "origin_tls_timeout",
+ "origin_response_incomplete",
+ "proxy_unavailable",
+ "restricted_route_unavailable",
+ "upstream_timeout",
+ "upstream_dns_failure",
+ "upstream_connect_failed",
+ "unknown",
+ ]
+ """
+ Proxy-layer error code: the X-Kernel-Proxy-Error response header value from a
+ branded error page served by the metro egress host-proxy. Values mirror what the
+ proxy emits: destination_blocked, destination_route_unavailable,
+ network_policy_denied, provider_blacklisted, provider_unreachable,
+ provider_rejected, origin_tls_timeout, origin_response_incomplete,
+ proxy_unavailable, restricted_route_unavailable, upstream_timeout,
+ upstream_dns_failure, upstream_connect_failed. A header value the browser image
+ does not recognize is reported as unknown, with the header value in raw_code.
+ """
+
+ request_id: str
+ """CDP request identifier matching the originating request."""
+
+ status: int
+ """
+ HTTP response status of the branded error page (502, or 403 for
+ network_policy_denied).
+ """
+
+ method: Optional[str] = None
+ """HTTP method of the failed request, when known."""
+
+ raw_code: Optional[str] = None
+ """Sanitized X-Kernel-Proxy-Error header value, present only when code is unknown.
+
+ Surrounding whitespace is removed, the value is lowercased, characters outside
+ [a-z0-9_] are replaced with \\__, and the result is truncated to at most 64
+ characters.
+ """
+
+ resource_type: Optional[str] = None
+ """CDP Network.ResourceType for the request, when known."""
+
+
+class BrowserProxyErrorEvent(BaseModel):
+ """A branded proxy-layer failure observed by the browser.
+
+ Emitted when the metro egress host-proxy serves a branded error page whose response carries the X-Kernel-Proxy-Error header: a 502 for proxy-layer failures, or a 403 when the session's network policy blocks the destination. Low-volume and carries a typed code. Its value is per-session and per-URL attribution for sessions that already capture the network stream: proxy failures are only observable while the CDP network collector is running, so this is an opt-in refinement of the raw network events rather than a default-on alerting signal.
+ """
+
+ category: Literal["network"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["proxy_error"]
+
+ data: Optional[Data] = None
+ """Browser event context stamped by the browser monitor onto all CDP-sourced
+ events.
+
+ Identifies the target, frame, and navigation epoch in which the event occurred.
+ """
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_service_crashed_event.py b/src/kernel/types/browsers/browser_service_crashed_event.py
new file mode 100644
index 00000000..7bc6f1cd
--- /dev/null
+++ b/src/kernel/types/browsers/browser_service_crashed_event.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserServiceCrashedEvent", "Data"]
+
+
+class Data(BaseModel):
+ phase: Literal["startup", "running", "gave_up"]
+ """Lifecycle phase the crash occurred in.
+
+ startup: the process died before reaching a healthy running state. running: a
+ previously healthy process died unexpectedly. gave_up: the process manager
+ exhausted its restart attempts and stopped trying.
+ """
+
+ service_name: str
+ """Program name of the crashed service (e.g. chromium, mutter, kernel-images-api)."""
+
+ pid: Optional[int] = None
+ """PID of the crashed process.
+
+ Absent when the process manager gave up after exhausting restart attempts.
+ """
+
+
+class BrowserServiceCrashedEvent(BaseModel):
+ """A managed service exited unexpectedly.
+
+ Intentional stops do not produce this event; only unexpected exits and terminal restart-give-up transitions do.
+ """
+
+ category: Literal["system"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["service_crashed"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_system_oom_kill_event.py b/src/kernel/types/browsers/browser_system_oom_kill_event.py
new file mode 100644
index 00000000..2cafb817
--- /dev/null
+++ b/src/kernel/types/browsers/browser_system_oom_kill_event.py
@@ -0,0 +1,99 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .browser_event_source import BrowserEventSource
+
+__all__ = ["BrowserSystemOomKillEvent", "Data", "DataTopTask"]
+
+
+class DataTopTask(BaseModel):
+ name: str
+ """Comm of the process (max 15 chars, truncated by the kernel)."""
+
+ pid: int
+ """PID of the process."""
+
+ rss_kb: int
+ """Resident set size in KiB at the moment of the kill."""
+
+
+class Data(BaseModel):
+ pid: int
+ """PID of the killed process."""
+
+ process_name: str
+ """
+ Comm of the killed process as reported by the kernel (max 15 chars, truncated by
+ the kernel).
+ """
+
+ rss_kb: int
+ """
+ Resident set size of the killed process in KiB (sum of anon-rss, file-rss, and
+ shmem-rss).
+ """
+
+ constraint: Optional[Literal["none", "memcg", "cpuset", "memory_policy"]] = None
+ """Why the kernel decided to OOM-kill.
+
+ none means global memory exhaustion; memcg means a cgroup memory limit was hit;
+ cpuset / memory_policy are NUMA/policy-driven kills. Absent on kernels older
+ than 5.0.
+ """
+
+ mem_free_kb: Optional[int] = None
+ """Free system memory in KiB at the time of the kill.
+
+ Assumes a 4 KiB page size. Does not include reclaimable caches. Absent if the
+ kernel did not emit a parseable Mem-Info section.
+ """
+
+ mem_total_kb: Optional[int] = None
+ """Total system memory in KiB at the time of the kill.
+
+ Assumes a 4 KiB page size. Absent if the kernel did not emit a parseable
+ Mem-Info section.
+ """
+
+ top_tasks: Optional[List[DataTopTask]] = None
+ """Top processes by resident-set-size at the moment of the kill, sorted descending.
+
+ Empty if the kernel did not emit the Tasks state table. Capped at 5 entries.
+ """
+
+ trigger_pid: Optional[int] = None
+ """PID of the triggering process.
+
+ Absent if the kernel did not emit the standard header line.
+ """
+
+ trigger_process_name: Optional[str] = None
+ """
+ Comm of the process whose allocation request caused the kernel to invoke the
+ OOM-killer. Often the same as process_name but can differ. Max 15 chars.
+ """
+
+
+class BrowserSystemOomKillEvent(BaseModel):
+ """The Linux kernel OOM-killer terminated a process inside the VM.
+
+ Fires for any process killed by the kernel due to memory exhaustion, including Chrome renderer subprocesses that are not supervised.
+ """
+
+ category: Literal["system"]
+
+ source: BrowserEventSource
+ """Provenance metadata identifying which producer emitted the event."""
+
+ ts: int
+ """Event timestamp in Unix microseconds."""
+
+ type: Literal["system_oom_kill"]
+
+ data: Optional[Data] = None
+
+ truncated: Optional[bool] = None
+ """True if the data field was truncated due to size limits."""
diff --git a/src/kernel/types/browsers/browser_telemetry_categories_config.py b/src/kernel/types/browsers/browser_telemetry_categories_config.py
new file mode 100644
index 00000000..062b360a
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_categories_config.py
@@ -0,0 +1,75 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .browser_telemetry_control_config import BrowserTelemetryControlConfig
+from .browser_telemetry_category_config import BrowserTelemetryCategoryConfig
+
+__all__ = ["BrowserTelemetryCategoriesConfig"]
+
+
+class BrowserTelemetryCategoriesConfig(BaseModel):
+ """Per-category telemetry capture settings layered onto the default set.
+
+ The operational signals (control, connection, system, captcha) are on by default and are opt-out: set one to enabled=false to stop capturing it. The CDP categories (console, network, page, interaction), screenshot and platform are off by default and are opt-in: set enabled=true to capture them.
+ """
+
+ captcha: Optional[BrowserTelemetryCategoryConfig] = None
+ """Captcha solver tasks and visible challenge outcomes. On by default."""
+
+ connection: Optional[BrowserTelemetryCategoryConfig] = None
+ """Client attach/detach lifecycle for the CDP proxy and live view. On by default."""
+
+ console: Optional[BrowserTelemetryCategoryConfig] = None
+ """Console output (log, warn, error) and uncaught exceptions.
+
+ CDP category; off by default.
+ """
+
+ control: Optional[BrowserTelemetryControlConfig] = None
+ """
+ Agent-driven actions against the browser — computer-control calls, Playwright
+ code execution, screenshots, clipboard access, and browser-control commands sent
+ over the CDP proxy. On by default.
+ """
+
+ interaction: Optional[BrowserTelemetryCategoryConfig] = None
+ """User interaction events including clicks, keydowns, and scroll-settled events.
+
+ CDP category; off by default.
+ """
+
+ network: Optional[BrowserTelemetryCategoryConfig] = None
+ """
+ HTTP request and response metadata including URL, method, status code, and
+ timing. Request post data is forwarded as-is from CDP. Text response bodies are
+ truncated at 8 KB for structured types (JSON, XML, form data) and 4 KB for other
+ text types. Binary responses (images, fonts, media) are excluded. CDP category;
+ off by default.
+ """
+
+ page: Optional[BrowserTelemetryCategoryConfig] = None
+ """
+ Page lifecycle events including navigation, DOMContentLoaded, load, layout
+ shifts, and LCP. CDP category; off by default.
+ """
+
+ platform: Optional[BrowserTelemetryCategoryConfig] = None
+ """
+ In-VM API calls that manage the browser VM rather than drive the browser
+ (recording, filesystem, process, telemetry and browser configuration). Mostly
+ platform-induced; off by default and must be opted into.
+ """
+
+ screenshot: Optional[BrowserTelemetryCategoryConfig] = None
+ """Periodic base64-encoded viewport screenshots.
+
+ High volume; off by default and must be opted into.
+ """
+
+ system: Optional[BrowserTelemetryCategoryConfig] = None
+ """Browser VM health, such as out-of-memory kills and managed-service crashes.
+
+ On by default.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_categories_config_param.py b/src/kernel/types/browsers/browser_telemetry_categories_config_param.py
new file mode 100644
index 00000000..d84d79d8
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_categories_config_param.py
@@ -0,0 +1,76 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from .browser_telemetry_control_config_param import BrowserTelemetryControlConfigParam
+from .browser_telemetry_category_config_param import BrowserTelemetryCategoryConfigParam
+
+__all__ = ["BrowserTelemetryCategoriesConfigParam"]
+
+
+class BrowserTelemetryCategoriesConfigParam(TypedDict, total=False):
+ """Per-category telemetry capture settings layered onto the default set.
+
+ The operational signals (control, connection, system, captcha) are on by default and are opt-out: set one to enabled=false to stop capturing it. The CDP categories (console, network, page, interaction), screenshot and platform are off by default and are opt-in: set enabled=true to capture them.
+ """
+
+ captcha: BrowserTelemetryCategoryConfigParam
+ """Captcha solver tasks and visible challenge outcomes. On by default."""
+
+ connection: BrowserTelemetryCategoryConfigParam
+ """Client attach/detach lifecycle for the CDP proxy and live view. On by default."""
+
+ console: BrowserTelemetryCategoryConfigParam
+ """Console output (log, warn, error) and uncaught exceptions.
+
+ CDP category; off by default.
+ """
+
+ control: BrowserTelemetryControlConfigParam
+ """
+ Agent-driven actions against the browser — computer-control calls, Playwright
+ code execution, screenshots, clipboard access, and browser-control commands sent
+ over the CDP proxy. On by default.
+ """
+
+ interaction: BrowserTelemetryCategoryConfigParam
+ """User interaction events including clicks, keydowns, and scroll-settled events.
+
+ CDP category; off by default.
+ """
+
+ network: BrowserTelemetryCategoryConfigParam
+ """
+ HTTP request and response metadata including URL, method, status code, and
+ timing. Request post data is forwarded as-is from CDP. Text response bodies are
+ truncated at 8 KB for structured types (JSON, XML, form data) and 4 KB for other
+ text types. Binary responses (images, fonts, media) are excluded. CDP category;
+ off by default.
+ """
+
+ page: BrowserTelemetryCategoryConfigParam
+ """
+ Page lifecycle events including navigation, DOMContentLoaded, load, layout
+ shifts, and LCP. CDP category; off by default.
+ """
+
+ platform: BrowserTelemetryCategoryConfigParam
+ """
+ In-VM API calls that manage the browser VM rather than drive the browser
+ (recording, filesystem, process, telemetry and browser configuration). Mostly
+ platform-induced; off by default and must be opted into.
+ """
+
+ screenshot: BrowserTelemetryCategoryConfigParam
+ """Periodic base64-encoded viewport screenshots.
+
+ High volume; off by default and must be opted into.
+ """
+
+ system: BrowserTelemetryCategoryConfigParam
+ """Browser VM health, such as out-of-memory kills and managed-service crashes.
+
+ On by default.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_category_config.py b/src/kernel/types/browsers/browser_telemetry_category_config.py
new file mode 100644
index 00000000..1a64f7d6
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_category_config.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["BrowserTelemetryCategoryConfig"]
+
+
+class BrowserTelemetryCategoryConfig(BaseModel):
+ """Per-category telemetry configuration."""
+
+ enabled: Optional[bool] = None
+ """Whether this category is captured.
+
+ Operational categories (control, connection, system, captcha) default to true;
+ set false to opt out. CDP categories (console, network, page, interaction),
+ screenshot and platform default to false; set true to opt in.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_category_config_param.py b/src/kernel/types/browsers/browser_telemetry_category_config_param.py
new file mode 100644
index 00000000..27f08538
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_category_config_param.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["BrowserTelemetryCategoryConfigParam"]
+
+
+class BrowserTelemetryCategoryConfigParam(TypedDict, total=False):
+ """Per-category telemetry configuration."""
+
+ enabled: bool
+ """Whether this category is captured.
+
+ Operational categories (control, connection, system, captcha) default to true;
+ set false to opt out. CDP categories (console, network, page, interaction),
+ screenshot and platform default to false; set true to opt in.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_cdp_control_config.py b/src/kernel/types/browsers/browser_telemetry_cdp_control_config.py
new file mode 100644
index 00000000..fe425713
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_cdp_control_config.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from ..._models import BaseModel
+from .browser_cdp_command_method import BrowserCdpCommandMethod
+
+__all__ = ["BrowserTelemetryCdpControlConfig"]
+
+
+class BrowserTelemetryCdpControlConfig(BaseModel):
+ """Settings for the cdp_command events the CDP proxy reports."""
+
+ excluded_methods: Optional[List[BrowserCdpCommandMethod]] = None
+ """Methods to leave out of the cdp_command stream.
+
+ Omit the list to keep the current one; send an empty list to report every
+ supported method again. Exclusion is a telemetry setting only: an excluded
+ command is still relayed to the browser unchanged, it simply produces no event.
+ Use it to drop the highest-volume methods — Input.dispatchMouseEvent during a
+ humanized cursor path, or Page.captureScreenshot under a screencast — without
+ turning the whole category off. Excluded commands are counted in
+ cdp_disconnect.telemetry_excluded.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_cdp_control_config_param.py b/src/kernel/types/browsers/browser_telemetry_cdp_control_config_param.py
new file mode 100644
index 00000000..300e9b60
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_cdp_control_config_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 List
+from typing_extensions import TypedDict
+
+from .browser_cdp_command_method import BrowserCdpCommandMethod
+
+__all__ = ["BrowserTelemetryCdpControlConfigParam"]
+
+
+class BrowserTelemetryCdpControlConfigParam(TypedDict, total=False):
+ """Settings for the cdp_command events the CDP proxy reports."""
+
+ excluded_methods: List[BrowserCdpCommandMethod]
+ """Methods to leave out of the cdp_command stream.
+
+ Omit the list to keep the current one; send an empty list to report every
+ supported method again. Exclusion is a telemetry setting only: an excluded
+ command is still relayed to the browser unchanged, it simply produces no event.
+ Use it to drop the highest-volume methods — Input.dispatchMouseEvent during a
+ humanized cursor path, or Page.captureScreenshot under a screencast — without
+ turning the whole category off. Excluded commands are counted in
+ cdp_disconnect.telemetry_excluded.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_config.py b/src/kernel/types/browsers/browser_telemetry_config.py
new file mode 100644
index 00000000..6eec504f
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_config.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .browser_telemetry_export_config import BrowserTelemetryExportConfig
+from .browser_telemetry_storage_config import BrowserTelemetryStorageConfig
+from .browser_telemetry_categories_config import BrowserTelemetryCategoriesConfig
+
+__all__ = ["BrowserTelemetryConfig"]
+
+
+class BrowserTelemetryConfig(BaseModel):
+ """Active telemetry configuration for a browser session."""
+
+ browser: Optional[BrowserTelemetryCategoriesConfig] = None
+ """Per-category enable/disable flags."""
+
+ export: Optional[BrowserTelemetryExportConfig] = None
+ """Where the session's captured telemetry is being exported.
+
+ Omitted when the export state is unknown.
+ """
+
+ storage: Optional[BrowserTelemetryStorageConfig] = None
+ """Whether the session's captured telemetry is persisted to Kernel storage.
+
+ Omitted for browsers created before this setting existed, which persist it.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_control_config.py b/src/kernel/types/browsers/browser_telemetry_control_config.py
new file mode 100644
index 00000000..7a8dfbb3
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_control_config.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .browser_telemetry_cdp_control_config import BrowserTelemetryCdpControlConfig
+
+__all__ = ["BrowserTelemetryControlConfig"]
+
+
+class BrowserTelemetryControlConfig(BaseModel):
+ """Configuration for the control category.
+
+ Same enabled semantics as any other category, plus settings for the browser-control commands the CDP proxy reports.
+ """
+
+ cdp: Optional[BrowserTelemetryCdpControlConfig] = None
+ """Settings for the cdp_command events the CDP proxy reports.
+
+ Merged independently of enabled, so a later update that only sets enabled keeps
+ the current exclusion list.
+ """
+
+ enabled: Optional[bool] = None
+ """Whether this category is captured.
+
+ Control is on by default; set false to opt out.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_control_config_param.py b/src/kernel/types/browsers/browser_telemetry_control_config_param.py
new file mode 100644
index 00000000..e5afaea5
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_control_config_param.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from .browser_telemetry_cdp_control_config_param import BrowserTelemetryCdpControlConfigParam
+
+__all__ = ["BrowserTelemetryControlConfigParam"]
+
+
+class BrowserTelemetryControlConfigParam(TypedDict, total=False):
+ """Configuration for the control category.
+
+ Same enabled semantics as any other category, plus settings for the browser-control commands the CDP proxy reports.
+ """
+
+ cdp: BrowserTelemetryCdpControlConfigParam
+ """Settings for the cdp_command events the CDP proxy reports.
+
+ Merged independently of enabled, so a later update that only sets enabled keeps
+ the current exclusion list.
+ """
+
+ enabled: bool
+ """Whether this category is captured.
+
+ Control is on by default; set false to opt out.
+ """
diff --git a/src/kernel/types/browsers/browser_telemetry_event.py b/src/kernel/types/browsers/browser_telemetry_event.py
new file mode 100644
index 00000000..d6c58a00
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_event.py
@@ -0,0 +1,88 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from .browser_api_call_event import BrowserAPICallEvent
+from .browser_page_lcp_event import BrowserPageLcpEvent
+from .browser_page_load_event import BrowserPageLoadEvent
+from .browser_cdp_command_event import BrowserCdpCommandEvent
+from .browser_cdp_connect_event import BrowserCdpConnectEvent
+from .browser_console_log_event import BrowserConsoleLogEvent
+from .browser_proxy_error_event import BrowserProxyErrorEvent
+from .browser_network_idle_event import BrowserNetworkIdleEvent
+from .browser_page_crashed_event import BrowserPageCrashedEvent
+from .browser_console_error_event import BrowserConsoleErrorEvent
+from .browser_cdp_disconnect_event import BrowserCdpDisconnectEvent
+from .browser_interaction_key_event import BrowserInteractionKeyEvent
+from .browser_network_request_event import BrowserNetworkRequestEvent
+from .browser_page_navigation_event import BrowserPageNavigationEvent
+from .browser_page_tab_opened_event import BrowserPageTabOpenedEvent
+from .browser_service_crashed_event import BrowserServiceCrashedEvent
+from .browser_system_oom_kill_event import BrowserSystemOomKillEvent
+from .browser_network_response_event import BrowserNetworkResponseEvent
+from .browser_interaction_click_event import BrowserInteractionClickEvent
+from .browser_live_view_connect_event import BrowserLiveViewConnectEvent
+from .browser_page_layout_shift_event import BrowserPageLayoutShiftEvent
+from .browser_platform_api_call_event import BrowserPlatformAPICallEvent
+from .browser_monitor_screenshot_event import BrowserMonitorScreenshotEvent
+from .browser_monitor_init_failed_event import BrowserMonitorInitFailedEvent
+from .browser_monitor_reconnected_event import BrowserMonitorReconnectedEvent
+from .browser_page_layout_settled_event import BrowserPageLayoutSettledEvent
+from .browser_captcha_solve_result_event import BrowserCaptchaSolveResultEvent
+from .browser_live_view_disconnect_event import BrowserLiveViewDisconnectEvent
+from .browser_monitor_disconnected_event import BrowserMonitorDisconnectedEvent
+from .browser_captcha_solve_started_event import BrowserCaptchaSolveStartedEvent
+from .browser_network_loading_failed_event import BrowserNetworkLoadingFailedEvent
+from .browser_page_dom_content_loaded_event import BrowserPageDomContentLoadedEvent
+from .browser_page_navigation_settled_event import BrowserPageNavigationSettledEvent
+from .browser_captcha_challenge_result_event import BrowserCaptchaChallengeResultEvent
+from .browser_monitor_reconnect_failed_event import BrowserMonitorReconnectFailedEvent
+from .browser_interaction_scroll_settled_event import BrowserInteractionScrollSettledEvent
+
+__all__ = ["BrowserTelemetryEvent"]
+
+BrowserTelemetryEvent: TypeAlias = Annotated[
+ Union[
+ BrowserConsoleLogEvent,
+ BrowserConsoleErrorEvent,
+ BrowserNetworkRequestEvent,
+ BrowserNetworkResponseEvent,
+ BrowserNetworkLoadingFailedEvent,
+ BrowserNetworkIdleEvent,
+ BrowserProxyErrorEvent,
+ BrowserPageNavigationEvent,
+ BrowserPageDomContentLoadedEvent,
+ BrowserPageLoadEvent,
+ BrowserPageTabOpenedEvent,
+ BrowserPageCrashedEvent,
+ BrowserPageLayoutShiftEvent,
+ BrowserPageLcpEvent,
+ BrowserPageLayoutSettledEvent,
+ BrowserPageNavigationSettledEvent,
+ BrowserInteractionClickEvent,
+ BrowserInteractionKeyEvent,
+ BrowserInteractionScrollSettledEvent,
+ BrowserMonitorScreenshotEvent,
+ BrowserMonitorDisconnectedEvent,
+ BrowserMonitorReconnectedEvent,
+ BrowserMonitorReconnectFailedEvent,
+ BrowserMonitorInitFailedEvent,
+ BrowserAPICallEvent,
+ BrowserPlatformAPICallEvent,
+ BrowserCdpCommandEvent,
+ BrowserCdpConnectEvent,
+ BrowserCdpDisconnectEvent,
+ BrowserLiveViewConnectEvent,
+ BrowserLiveViewDisconnectEvent,
+ BrowserCaptchaSolveStartedEvent,
+ BrowserCaptchaSolveResultEvent,
+ BrowserCaptchaChallengeResultEvent,
+ BrowserSystemOomKillEvent,
+ BrowserServiceCrashedEvent,
+ ],
+ PropertyInfo(discriminator="type"),
+]
diff --git a/src/kernel/types/browsers/browser_telemetry_export_config.py b/src/kernel/types/browsers/browser_telemetry_export_config.py
new file mode 100644
index 00000000..8aa93704
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_export_config.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .browser_telemetry_otlp_export_config import BrowserTelemetryOtlpExportConfig
+
+__all__ = ["BrowserTelemetryExportConfig"]
+
+
+class BrowserTelemetryExportConfig(BaseModel):
+ """Active export state for a session's captured telemetry, by protocol."""
+
+ otlp: Optional[BrowserTelemetryOtlpExportConfig] = None
+ """Active OTLP export state."""
diff --git a/src/kernel/types/browsers/browser_telemetry_otlp_export_config.py b/src/kernel/types/browsers/browser_telemetry_otlp_export_config.py
new file mode 100644
index 00000000..3626a5f6
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_otlp_export_config.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["BrowserTelemetryOtlpExportConfig"]
+
+
+class BrowserTelemetryOtlpExportConfig(BaseModel):
+ """Active OTLP export state for a browser session."""
+
+ destination: Optional[str] = None
+ """ID of the OTLP destination the session is bound to.
+
+ Omitted when the session is not exporting.
+ """
+
+ enabled: Optional[bool] = None
+ """Whether the session is exporting captured telemetry over OTLP."""
diff --git a/src/kernel/types/browsers/browser_telemetry_storage_config.py b/src/kernel/types/browsers/browser_telemetry_storage_config.py
new file mode 100644
index 00000000..f79f307e
--- /dev/null
+++ b/src/kernel/types/browsers/browser_telemetry_storage_config.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["BrowserTelemetryStorageConfig"]
+
+
+class BrowserTelemetryStorageConfig(BaseModel):
+ """Kernel storage state for a session's captured telemetry."""
+
+ enabled: Optional[bool] = None
+ """Whether captured telemetry is persisted to Kernel storage.
+
+ When off, the session's events are only available on the live stream and through
+ any configured export.
+ """
diff --git a/src/kernel/types/browsers/computer_batch_params.py b/src/kernel/types/browsers/computer_batch_params.py
index 7fc6abb5..dbd91756 100644
--- a/src/kernel/types/browsers/computer_batch_params.py
+++ b/src/kernel/types/browsers/computer_batch_params.py
@@ -110,7 +110,10 @@ class ActionPressKey(TypedDict, total=False):
Each item should be a key symbol supported by xdotool (see X11 keysym
definitions). Examples include "Return", "Shift", "Ctrl", "Alt", "F5". Items in
- this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab".
+ this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab". Use X11
+ names for punctuation in combinations, such as "Ctrl+minus" or "Ctrl+plus". A
+ literal hyphen is also accepted as an alias, so "Ctrl+-" is normalized to
+ "Ctrl+minus".
"""
duration: int
diff --git a/src/kernel/types/browsers/computer_press_key_params.py b/src/kernel/types/browsers/computer_press_key_params.py
index ea2c9b45..8722e1b3 100644
--- a/src/kernel/types/browsers/computer_press_key_params.py
+++ b/src/kernel/types/browsers/computer_press_key_params.py
@@ -15,7 +15,10 @@ class ComputerPressKeyParams(TypedDict, total=False):
Each item should be a key symbol supported by xdotool (see X11 keysym
definitions). Examples include "Return", "Shift", "Ctrl", "Alt", "F5". Items in
- this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab".
+ this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab". Use X11
+ names for punctuation in combinations, such as "Ctrl+minus" or "Ctrl+plus". A
+ literal hyphen is also accepted as an alias, so "Ctrl+-" is normalized to
+ "Ctrl+minus".
"""
duration: int
diff --git a/src/kernel/types/browsers/custom_tool_source.py b/src/kernel/types/browsers/custom_tool_source.py
new file mode 100644
index 00000000..0ab59ab7
--- /dev/null
+++ b/src/kernel/types/browsers/custom_tool_source.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["CustomToolSource"]
+
+
+class CustomToolSource(BaseModel):
+ id: str
+
+ namespace: str
diff --git a/src/kernel/types/browsers/invocation_result.py b/src/kernel/types/browsers/invocation_result.py
new file mode 100644
index 00000000..c8c06188
--- /dev/null
+++ b/src/kernel/types/browsers/invocation_result.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["InvocationResult"]
+
+
+class InvocationResult(BaseModel):
+ invocation_id: str
+
+ status: Literal["completed", "canceled", "error", "awaiting_submission"]
+ """
+ awaiting_submission means a non-autosubmit declarative form was populated but
+ not submitted. Inspect the form, obtain any required confirmation, then submit
+ through Playwright or computer interaction without invoking the tool again. The
+ other statuses are terminal results.
+ """
+
+ error_text: Optional[str] = None
+
+ output: Optional[object] = None
+ """Untrusted page-provided output.
+
+ Callers must treat it as potentially malicious input.
+ """
diff --git a/src/kernel/types/browsers/playwright/__init__.py b/src/kernel/types/browsers/playwright/__init__.py
new file mode 100644
index 00000000..74b73b59
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/__init__.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .executor import Executor as Executor
+from .executor_list import ExecutorList as ExecutorList
+from .executor_delete_params import ExecutorDeleteParams as ExecutorDeleteParams
diff --git a/src/kernel/types/browsers/playwright/executor.py b/src/kernel/types/browsers/playwright/executor.py
new file mode 100644
index 00000000..0085af9a
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/executor.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ...._models import BaseModel
+
+__all__ = ["Executor"]
+
+
+class Executor(BaseModel):
+ """A Playwright executor on the browser"""
+
+ busy: bool
+ """Whether a call is running on the executor"""
+
+ created_at: datetime
+ """When the executor was created"""
+
+ last_used_at: datetime
+ """When the most recent call on the executor started"""
+
+ name: str
+ """Name of a Playwright executor.
+
+ Calls with the same name run in the same executor, one at a time; the first call
+ with a new name creates it. Calls on different executors run concurrently.
+ 'default' names the executor that runs calls without a name.
+ """
+
+ target_id: Optional[str] = None
+ """CDP page target ID of the executor's tab, once it has one.
+
+ The default executor owns no tab.
+ """
+
+ url: Optional[str] = None
+ """Current URL of the executor's tab, when it is open"""
diff --git a/src/kernel/types/browsers/playwright/executor_delete_params.py b/src/kernel/types/browsers/playwright/executor_delete_params.py
new file mode 100644
index 00000000..e553f435
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/executor_delete_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ExecutorDeleteParams"]
+
+
+class ExecutorDeleteParams(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ close_tab: bool
+ """Close the executor's tab. Defaults to true."""
diff --git a/src/kernel/types/browsers/playwright/executor_list.py b/src/kernel/types/browsers/playwright/executor_list.py
new file mode 100644
index 00000000..c3e89c1f
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/executor_list.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .executor import Executor
+from ...._models import BaseModel
+
+__all__ = ["ExecutorList"]
+
+
+class ExecutorList(BaseModel):
+ """The default executor followed by the named executors"""
+
+ executors: List[Executor]
diff --git a/src/kernel/types/browsers/playwright_execute_params.py b/src/kernel/types/browsers/playwright_execute_params.py
index 948a74c1..85980a9d 100644
--- a/src/kernel/types/browsers/playwright_execute_params.py
+++ b/src/kernel/types/browsers/playwright_execute_params.py
@@ -17,5 +17,13 @@ class PlaywrightExecuteParams(TypedDict, total=False):
"await page.goto('https://example.com'); return await page.title();"
"""
+ executor: str
+ """Name of a Playwright executor.
+
+ Calls with the same name run in the same executor, one at a time; the first call
+ with a new name creates it. Calls on different executors run concurrently.
+ 'default' names the executor that runs calls without a name.
+ """
+
timeout_sec: int
"""Maximum execution time in seconds. Default is 60."""
diff --git a/src/kernel/types/browsers/playwright_execute_response.py b/src/kernel/types/browsers/playwright_execute_response.py
index d53080de..63080119 100644
--- a/src/kernel/types/browsers/playwright_execute_response.py
+++ b/src/kernel/types/browsers/playwright_execute_response.py
@@ -2,6 +2,7 @@
from typing import Optional
+from .tab import Tab
from ..._models import BaseModel
__all__ = ["PlaywrightExecuteResponse"]
@@ -24,3 +25,9 @@ class PlaywrightExecuteResponse(BaseModel):
stdout: Optional[str] = None
"""Standard output from the execution"""
+
+ tab: Optional[Tab] = None
+ """The tab 'page' was bound to for this call.
+
+ Absent if the call failed before binding a tab.
+ """
diff --git a/src/kernel/types/browsers/process_kill_params.py b/src/kernel/types/browsers/process_kill_params.py
index 84be2770..def8a7c1 100644
--- a/src/kernel/types/browsers/process_kill_params.py
+++ b/src/kernel/types/browsers/process_kill_params.py
@@ -8,7 +8,7 @@
class ProcessKillParams(TypedDict, total=False):
- id: Required[str]
+ id_or_name: Required[str]
signal: Required[Literal["TERM", "KILL", "INT", "HUP"]]
"""Signal to send."""
diff --git a/src/kernel/types/browsers/process_resize_params.py b/src/kernel/types/browsers/process_resize_params.py
index 4fdb8ad5..5a7532ed 100644
--- a/src/kernel/types/browsers/process_resize_params.py
+++ b/src/kernel/types/browsers/process_resize_params.py
@@ -8,7 +8,7 @@
class ProcessResizeParams(TypedDict, total=False):
- id: Required[str]
+ id_or_name: Required[str]
cols: Required[int]
"""New terminal columns."""
diff --git a/src/kernel/types/browsers/process_stdin_params.py b/src/kernel/types/browsers/process_stdin_params.py
index 9ece9a57..c999fa64 100644
--- a/src/kernel/types/browsers/process_stdin_params.py
+++ b/src/kernel/types/browsers/process_stdin_params.py
@@ -8,7 +8,7 @@
class ProcessStdinParams(TypedDict, total=False):
- id: Required[str]
+ id_or_name: Required[str]
data_b64: Required[str]
"""Base64-encoded data to write."""
diff --git a/src/kernel/types/browsers/replay_start_params.py b/src/kernel/types/browsers/replay_start_params.py
index d77b6c49..c37c554d 100644
--- a/src/kernel/types/browsers/replay_start_params.py
+++ b/src/kernel/types/browsers/replay_start_params.py
@@ -16,3 +16,9 @@ class ReplayStartParams(TypedDict, total=False):
max_duration_in_seconds: int
"""Maximum recording duration in seconds."""
+
+ record_audio: bool
+ """Record audio in addition to video.
+
+ When false (the default), the recording is video-only.
+ """
diff --git a/src/kernel/types/browsers/tab.py b/src/kernel/types/browsers/tab.py
new file mode 100644
index 00000000..9f53eeca
--- /dev/null
+++ b/src/kernel/types/browsers/tab.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["Tab"]
+
+
+class Tab(BaseModel):
+ """The tab 'page' was bound to for this call.
+
+ Absent if the call failed before binding a tab.
+ """
+
+ created: bool
+ """Whether this call opened the tab.
+
+ For a named executor this happens on its first call and after its previous tab
+ was closed. For the default executor it happens only when the browser had no
+ open page.
+ """
+
+ target_id: str
+ """CDP page target ID of the tab"""
diff --git a/src/kernel/types/browsers/telemetry_events_params.py b/src/kernel/types/browsers/telemetry_events_params.py
new file mode 100644
index 00000000..1253aa1a
--- /dev/null
+++ b/src/kernel/types/browsers/telemetry_events_params.py
@@ -0,0 +1,71 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal, TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["TelemetryEventsParams"]
+
+
+class TelemetryEventsParams(TypedDict, total=False):
+ category: List[
+ Literal[
+ "console",
+ "network",
+ "page",
+ "interaction",
+ "control",
+ "platform",
+ "connection",
+ "system",
+ "screenshot",
+ "captcha",
+ "monitor",
+ ]
+ ]
+ """Restrict results to these event categories.
+
+ Repeat the parameter for multiple values.
+ """
+
+ limit: int
+ """Maximum number of events per page. Defaults to 20."""
+
+ offset: int
+ """
+ Opaque pagination cursor: pass the X-Next-Offset value from the previous
+ response to fetch the next page. When set, paging continues from this cursor and
+ since is ignored, while until still bounds the page. It is not an event's seq
+ field, so do not derive it from the response body.
+ """
+
+ order: str
+ """Read direction.
+
+ asc (default) reads oldest first, starting from since or the offset cursor. desc
+ reads newest first: each request returns one page of up to limit records ending
+ at the offset cursor (or until, or the newest archived event); combining desc
+ with since is rejected with a 400.
+ """
+
+ since: str
+ """
+ Start of the window: an RFC-3339 timestamp, or a duration like 5m meaning that
+ long ago. Defaults to 5m. Ignored when offset is set.
+ """
+
+ type: SequenceNotStr[str]
+ """
+ Restrict results to these event types, such as page_crashed or
+ captcha_challenge_result. Repeat the parameter for multiple values. Combines
+ with category: when both are set an event must match both.
+ """
+
+ until: str
+ """
+ End of the window (exclusive): an RFC-3339 timestamp, or a duration like 5m
+ meaning that long ago.
+ """
diff --git a/src/kernel/types/browsers/telemetry_events_response.py b/src/kernel/types/browsers/telemetry_events_response.py
new file mode 100644
index 00000000..3002f470
--- /dev/null
+++ b/src/kernel/types/browsers/telemetry_events_response.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._models import BaseModel
+
+__all__ = ["TelemetryEventsResponse"]
+
+
+class TelemetryEventsResponse(BaseModel):
+ """Envelope wrapping a browser telemetry event with its monotonic sequence number.
+
+ Each SSE data: frame carries one envelope as JSON. The seq value is also emitted as the SSE id: field so clients can pass it as Last-Event-ID on reconnect.
+ """
+
+ event: "BrowserTelemetryEvent"
+ """Union type representing any browser telemetry event.
+
+ Discriminated on `type`. Each event's `category` determines when it is captured.
+ The CDP collector-health events (monitor_disconnected, monitor_reconnected,
+ monitor_reconnect_failed, monitor_init_failed) use the `monitor` category, which
+ is not user-configurable: it flows automatically whenever any CDP category
+ (console, network, page, interaction) is captured, and is silent otherwise.
+ monitor_screenshot uses the opt-in `screenshot` category. All other event types
+ are controlled by their per-category enable/disable flags.
+ """
+
+ seq: int
+ """Process-monotonic sequence number assigned by the browser VM.
+
+ Pass as Last-Event-ID on reconnect to resume without gaps. Gaps in received seq
+ values indicate dropped events.
+ """
+
+
+from .browser_telemetry_event import BrowserTelemetryEvent
diff --git a/src/kernel/types/browsers/telemetry_stream_params.py b/src/kernel/types/browsers/telemetry_stream_params.py
new file mode 100644
index 00000000..3e99dd5e
--- /dev/null
+++ b/src/kernel/types/browsers/telemetry_stream_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._types import SequenceNotStr
+from ..._utils import PropertyInfo
+
+__all__ = ["TelemetryStreamParams"]
+
+
+class TelemetryStreamParams(TypedDict, total=False):
+ replay: str
+ """
+ Pass `all` to start from the oldest retained event instead of only new events;
+ any other value is treated as from-now. The buffer is bounded, so the first
+ event id may be greater than 1 if older events were evicted.
+ """
+
+ type: SequenceNotStr[str]
+ """
+ Deliver only these event types, such as captcha_solve_started or
+ captcha_challenge_result. Repeat the parameter or pass comma-separated values
+ for multiple types. Keepalive frames are always delivered. Filtered-out events
+ are not sent. An id-only frame carrying the latest skipped id is sent about
+ every 15 seconds while skipped events keep arriving, and otherwise with the next
+ keepalive or when the stream ends, so the connection stays active and
+ Last-Event-ID moves past them; a reconnect resumes after the skipped events.
+ """
+
+ last_event_id: Annotated[str, PropertyInfo(alias="Last-Event-ID")]
diff --git a/src/kernel/types/browsers/telemetry_stream_response.py b/src/kernel/types/browsers/telemetry_stream_response.py
new file mode 100644
index 00000000..25dd6650
--- /dev/null
+++ b/src/kernel/types/browsers/telemetry_stream_response.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._models import BaseModel
+
+__all__ = ["TelemetryStreamResponse"]
+
+
+class TelemetryStreamResponse(BaseModel):
+ """Envelope wrapping a browser telemetry event with its monotonic sequence number.
+
+ Each SSE data: frame carries one envelope as JSON. The seq value is also emitted as the SSE id: field so clients can pass it as Last-Event-ID on reconnect.
+ """
+
+ event: "BrowserTelemetryEvent"
+ """Union type representing any browser telemetry event.
+
+ Discriminated on `type`. Each event's `category` determines when it is captured.
+ The CDP collector-health events (monitor_disconnected, monitor_reconnected,
+ monitor_reconnect_failed, monitor_init_failed) use the `monitor` category, which
+ is not user-configurable: it flows automatically whenever any CDP category
+ (console, network, page, interaction) is captured, and is silent otherwise.
+ monitor_screenshot uses the opt-in `screenshot` category. All other event types
+ are controlled by their per-category enable/disable flags.
+ """
+
+ seq: int
+ """Process-monotonic sequence number assigned by the browser VM.
+
+ Pass as Last-Event-ID on reconnect to resume without gaps. Gaps in received seq
+ values indicate dropped events.
+ """
+
+
+from .browser_telemetry_event import BrowserTelemetryEvent
diff --git a/src/kernel/types/browsers/tool.py b/src/kernel/types/browsers/tool.py
new file mode 100644
index 00000000..5bde27d0
--- /dev/null
+++ b/src/kernel/types/browsers/tool.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .tool_source import ToolSource
+from .tool_metadata import ToolMetadata
+
+__all__ = ["Tool"]
+
+
+class Tool(BaseModel):
+ source: ToolSource
+
+ tool: ToolMetadata
+ """
+ Tool metadata follows the
+ [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool)
+ and the
+ [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool).
+ outputSchema is optional for page and custom tools.
+ """
+
+ tool_ref: str
+ """Opaque reference for invoking this exact live registration.
+
+ It becomes invalid when its document or browser process is replaced.
+ """
diff --git a/src/kernel/types/browsers/tool_annotations.py b/src/kernel/types/browsers/tool_annotations.py
new file mode 100644
index 00000000..7c12d425
--- /dev/null
+++ b/src/kernel/types/browsers/tool_annotations.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ToolAnnotations"]
+
+
+class ToolAnnotations(BaseModel):
+ """
+ Tool-provided behavioral hints from the [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool)
+ and the [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations).
+ These hints are untrusted and are not enforced by Kernel.
+ """
+
+ autosubmit: Optional[bool] = None
+
+ consequential_hint: Optional[bool] = FieldInfo(alias="consequentialHint", default=None)
+
+ destructive_hint: Optional[bool] = FieldInfo(alias="destructiveHint", default=None)
+
+ idempotent_hint: Optional[bool] = FieldInfo(alias="idempotentHint", default=None)
+
+ open_world_hint: Optional[bool] = FieldInfo(alias="openWorldHint", default=None)
+
+ read_only_hint: Optional[bool] = FieldInfo(alias="readOnlyHint", default=None)
+
+ untrusted_content_hint: Optional[bool] = FieldInfo(alias="untrustedContentHint", default=None)
diff --git a/src/kernel/types/browsers/tool_frame.py b/src/kernel/types/browsers/tool_frame.py
new file mode 100644
index 00000000..687994f1
--- /dev/null
+++ b/src/kernel/types/browsers/tool_frame.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["ToolFrame"]
+
+
+class ToolFrame(BaseModel):
+ frame_id: int
+ """
+ Monotonically increasing identifier for this embedded frame during the current
+ browser process.
+ """
+
+ url: str
+ """Current frame URL with the fragment omitted."""
diff --git a/src/kernel/types/browsers/tool_metadata.py b/src/kernel/types/browsers/tool_metadata.py
new file mode 100644
index 00000000..747f461e
--- /dev/null
+++ b/src/kernel/types/browsers/tool_metadata.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .tool_annotations import ToolAnnotations
+
+__all__ = ["ToolMetadata"]
+
+
+class ToolMetadata(BaseModel):
+ """
+ Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool)
+ and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool).
+ outputSchema is optional for page and custom tools.
+ """
+
+ description: str
+
+ input_schema: Dict[str, object] = FieldInfo(alias="inputSchema")
+
+ name: str
+
+ annotations: Optional[ToolAnnotations] = None
+ """
+ Tool-provided behavioral hints from the
+ [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool)
+ and the
+ [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations).
+ These hints are untrusted and are not enforced by Kernel.
+ """
+
+ output_schema: Optional[Dict[str, object]] = FieldInfo(alias="outputSchema", default=None)
+
+ title: Optional[str] = None
diff --git a/src/kernel/types/browsers/tool_source.py b/src/kernel/types/browsers/tool_source.py
new file mode 100644
index 00000000..65b57412
--- /dev/null
+++ b/src/kernel/types/browsers/tool_source.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .tool_frame import ToolFrame
+from .custom_tool_source import CustomToolSource
+
+__all__ = ["ToolSource"]
+
+
+class ToolSource(BaseModel):
+ frame: Optional[ToolFrame] = None
+ """
+ Embedded frame that registered the tool, or null when the top-level page
+ registered it.
+ """
+
+ page_title: str
+ """Current title of the top-level page."""
+
+ page_url: str
+ """Current URL of the top-level page with the fragment omitted."""
+
+ tab_id: int
+ """
+ Monotonically increasing identifier for the tab during the current browser
+ process.
+ """
+
+ window_id: int
+ """
+ Monotonically increasing identifier for the browser window during the current
+ browser process.
+ """
+
+ custom: Optional[CustomToolSource] = None
+
+ target_id: Optional[str] = None
+ """
+ CDP target ID for a custom tool's registration tab; omitted for page-provided
+ tools.
+ """
diff --git a/src/kernel/types/browsers/tools_response.py b/src/kernel/types/browsers/tools_response.py
new file mode 100644
index 00000000..85952306
--- /dev/null
+++ b/src/kernel/types/browsers/tools_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .tool import Tool
+from ..._models import BaseModel
+
+__all__ = ["ToolsResponse"]
+
+
+class ToolsResponse(BaseModel):
+ tools: List[Tool]
diff --git a/src/kernel/types/browsers/webmcp/__init__.py b/src/kernel/types/browsers/webmcp/__init__.py
new file mode 100644
index 00000000..cbad2766
--- /dev/null
+++ b/src/kernel/types/browsers/webmcp/__init__.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .match import Match as Match
+from .definition import Definition as Definition
+from .custom_tools_response import CustomToolsResponse as CustomToolsResponse
+from .custom_tool_add_params import CustomToolAddParams as CustomToolAddParams
diff --git a/src/kernel/types/browsers/webmcp/custom_tool_add_params.py b/src/kernel/types/browsers/webmcp/custom_tool_add_params.py
new file mode 100644
index 00000000..3fc195e2
--- /dev/null
+++ b/src/kernel/types/browsers/webmcp/custom_tool_add_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["CustomToolAddParams"]
+
+
+class CustomToolAddParams(TypedDict, total=False):
+ namespace: Required[str]
+
+ source: Required[str]
+ """
+ JavaScript expression that evaluates to a non-empty array of custom tool
+ definitions. Limited to 8,000,000 bytes when UTF-8 encoded, so multi-byte
+ characters reduce the allowed character count.
+ """
+
+ force_overwrite_namespace: bool
+ """
+ Atomically replace all existing tools in this namespace with this batch when
+ true.
+ """
diff --git a/src/kernel/types/browsers/webmcp/custom_tools_response.py b/src/kernel/types/browsers/webmcp/custom_tools_response.py
new file mode 100644
index 00000000..4b5df6bd
--- /dev/null
+++ b/src/kernel/types/browsers/webmcp/custom_tools_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from ...._models import BaseModel
+from .definition import Definition
+
+__all__ = ["CustomToolsResponse"]
+
+
+class CustomToolsResponse(BaseModel):
+ tools: List[Definition]
diff --git a/src/kernel/types/browsers/webmcp/definition.py b/src/kernel/types/browsers/webmcp/definition.py
new file mode 100644
index 00000000..3a5cbeaa
--- /dev/null
+++ b/src/kernel/types/browsers/webmcp/definition.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .match import Match
+from ...._models import BaseModel
+from ..tool_metadata import ToolMetadata
+
+__all__ = ["Definition"]
+
+
+class Definition(BaseModel):
+ id: str
+
+ kind: str
+
+ match: Match
+
+ namespace: str
+
+ tool: ToolMetadata
+ """
+ Tool metadata follows the
+ [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool)
+ and the
+ [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool).
+ outputSchema is optional for page and custom tools.
+ """
diff --git a/src/kernel/types/browsers/webmcp/match.py b/src/kernel/types/browsers/webmcp/match.py
new file mode 100644
index 00000000..51523b24
--- /dev/null
+++ b/src/kernel/types/browsers/webmcp/match.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from ...._models import BaseModel
+
+__all__ = ["Match"]
+
+
+class Match(BaseModel):
+ url_patterns: List[str]
diff --git a/src/kernel/types/browsers/webmcp_invoke_tool_params.py b/src/kernel/types/browsers/webmcp_invoke_tool_params.py
new file mode 100644
index 00000000..99f0f25f
--- /dev/null
+++ b/src/kernel/types/browsers/webmcp_invoke_tool_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Required, TypedDict
+
+__all__ = ["WebmcpInvokeToolParams"]
+
+
+class WebmcpInvokeToolParams(TypedDict, total=False):
+ input: Required[Dict[str, object]]
+ """Tool input, limited to 1 MiB after JSON serialization."""
+
+ tool_ref: Required[str]
+
+ timeout_sec: int
diff --git a/src/kernel/types/browsers/webmcp_list_tools_params.py b/src/kernel/types/browsers/webmcp_list_tools_params.py
new file mode 100644
index 00000000..f499c9ca
--- /dev/null
+++ b/src/kernel/types/browsers/webmcp_list_tools_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 TypedDict
+
+__all__ = ["WebmcpListToolsParams"]
+
+
+class WebmcpListToolsParams(TypedDict, total=False):
+ exclude_custom: bool
+ """Exclude custom tools when true."""
diff --git a/src/kernel/types/config_registry/__init__.py b/src/kernel/types/config_registry/__init__.py
new file mode 100644
index 00000000..2950bf63
--- /dev/null
+++ b/src/kernel/types/config_registry/__init__.py
@@ -0,0 +1,5 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .analysis_list_params import AnalysisListParams as AnalysisListParams
diff --git a/src/kernel/types/config_registry/analysis_list_params.py b/src/kernel/types/config_registry/analysis_list_params.py
new file mode 100644
index 00000000..b94f5dca
--- /dev/null
+++ b/src/kernel/types/config_registry/analysis_list_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["AnalysisListParams"]
+
+
+class AnalysisListParams(TypedDict, total=False):
+ limit: int
+
+ offset: int
+
+ search: str
+ """Case-insensitive substring search over requested URLs."""
diff --git a/src/kernel/types/config_registry_list_params.py b/src/kernel/types/config_registry_list_params.py
new file mode 100644
index 00000000..7277ef4b
--- /dev/null
+++ b/src/kernel/types/config_registry_list_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ConfigRegistryListParams"]
+
+
+class ConfigRegistryListParams(TypedDict, total=False):
+ limit: int
+
+ offset: int
+
+ search: str
+ """
+ Case-insensitive substring search over normalized targets, including domain,
+ subdomain, and path.
+ """
+
+ sort_by: Literal["target", "analysis_status", "recommended_config", "last_requested_at", "success_rate"]
+
+ sort_order: Literal["asc", "desc"]
diff --git a/src/kernel/types/config_registry_lookup_params.py b/src/kernel/types/config_registry_lookup_params.py
new file mode 100644
index 00000000..13703a5a
--- /dev/null
+++ b/src/kernel/types/config_registry_lookup_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["ConfigRegistryLookupParams"]
+
+
+class ConfigRegistryLookupParams(TypedDict, total=False):
+ url: Required[str]
+ """Public HTTP(S) URL to look up."""
+
+ allowed_proxy_countries: SequenceNotStr[str]
+ """ISO 3166 country codes Kernel may use when returning a proxy configuration.
+
+ When omitted, Kernel uses its default country selection.
+ """
diff --git a/src/kernel/types/config_registry_resolve_params.py b/src/kernel/types/config_registry_resolve_params.py
new file mode 100644
index 00000000..e1a063dc
--- /dev/null
+++ b/src/kernel/types/config_registry_resolve_params.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["ConfigRegistryResolveParams"]
+
+
+class ConfigRegistryResolveParams(TypedDict, total=False):
+ url: Required[str]
+ """Public HTTP(S) URL to refresh."""
+
+ allowed_proxy_countries: SequenceNotStr[str]
+ """
+ ISO 3166 country codes Kernel may use when searching for or returning a proxy
+ configuration. Kernel may test a subset of allowed countries. When omitted,
+ Kernel uses its default country selection.
+ """
+
+ intent: str
+ """
+ Plain-language description of the workload you intend to run against this
+ target, in a sentence or two. Requires an https target, because the pass treats
+ any non-HTTPS destination as off-site and will not drive an http one. Kernel
+ uses it to drive the browser further into the site, where it can observe
+ protections that only appear once a session interacts. When this target already
+ has a recommended configuration, the run confirms that one instead of
+ re-deriving the whole matrix, so supplying an intent narrows what can be
+ recommended.
+ """
diff --git a/src/kernel/types/config_registry_response.py b/src/kernel/types/config_registry_response.py
new file mode 100644
index 00000000..cad09105
--- /dev/null
+++ b/src/kernel/types/config_registry_response.py
@@ -0,0 +1,48 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .target import Target
+from .._models import BaseModel
+from .analysis import Analysis
+from .recommendation import Recommendation
+from .recommendation_result import RecommendationResult
+
+__all__ = ["ConfigRegistryResponse"]
+
+
+class ConfigRegistryResponse(BaseModel):
+ analysis: Optional[Analysis] = None
+ """Pollable analysis after workflow submission is acknowledged.
+
+ Null when no refresh was submitted.
+ """
+
+ recommendation: Optional[RecommendationResult] = None
+ """A recommendation or a structured no-recommendation result."""
+
+ target: Target
+
+ working_configurations: List[Recommendation]
+ """
+ Working configurations for the target, ordered with the recommended
+ configuration first.
+ """
+
+ guidance: Optional[str] = None
+ """Short advisory markdown to facilitate navigating this target.
+
+ Returned even when no configuration reached the target, since knowing what
+ prevented success is useful without a configuration. Not verified against this
+ target. Null when nothing applicable was observed or no notes exist.
+ """
+
+ workload_outcome: Optional[
+ Literal["completed", "turn_limit", "auth_required", "payment_required", "blocked", "error"]
+ ] = None
+ """How far the workload pass got, when an intent was supplied and a pass ran.
+
+ A run outcome rather than advice, so it is reported whether or not any guidance
+ could be assembled. Null when no intent was supplied or no pass ran.
+ """
diff --git a/src/kernel/types/created_api_key.py b/src/kernel/types/created_api_key.py
new file mode 100644
index 00000000..674943d5
--- /dev/null
+++ b/src/kernel/types/created_api_key.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .api_key import APIKey
+
+__all__ = ["CreatedAPIKey"]
+
+
+class CreatedAPIKey(APIKey):
+ """API key returned immediately after creation. Includes the plaintext key once."""
+
+ key: str
+ """Plaintext API key. Only returned once when the key is created."""
diff --git a/src/kernel/types/credential.py b/src/kernel/types/credential.py
index 323617c5..3b54587c 100644
--- a/src/kernel/types/credential.py
+++ b/src/kernel/types/credential.py
@@ -1,7 +1,8 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import List, Optional
from datetime import datetime
+from typing_extensions import Literal
from .._models import BaseModel
@@ -40,11 +41,36 @@ class Credential(BaseModel):
identity provider's login page.
"""
+ totp_algorithm: Optional[Literal["SHA1", "SHA256", "SHA512"]] = None
+ """HMAC algorithm used to generate TOTP codes.
+
+ Defaults to SHA1 for credentials created before this metadata was stored.
+ """
+
totp_code: Optional[str] = None
- """Current 6-digit TOTP code.
+ """Current TOTP code.
Only included in create/update responses when totp_secret was just set.
"""
totp_code_expires_at: Optional[datetime] = None
"""When the totp_code expires. Only included when totp_code is present."""
+
+ totp_digits: Optional[int] = None
+ """Number of digits in generated TOTP codes.
+
+ Defaults to 6 for credentials created before this metadata was stored.
+ """
+
+ totp_period: Optional[int] = None
+ """TOTP rotation period in seconds.
+
+ Defaults to 30 for credentials created before this metadata was stored.
+ """
+
+ value_keys: Optional[List[str]] = None
+ """The field names stored in this credential's values (e.g., username, password).
+
+ Values themselves are never returned. Included on single-credential responses
+ (create, get by id or name, update); omitted from list responses.
+ """
diff --git a/src/kernel/types/credential_create_params.py b/src/kernel/types/credential_create_params.py
index 9d306844..526b7149 100644
--- a/src/kernel/types/credential_create_params.py
+++ b/src/kernel/types/credential_create_params.py
@@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Dict
-from typing_extensions import Required, TypedDict
+from typing_extensions import Literal, Required, TypedDict
__all__ = ["CredentialCreateParams"]
@@ -26,8 +26,29 @@ class CredentialCreateParams(TypedDict, total=False):
identity provider's login page.
"""
- totp_secret: str
- """Base32-encoded TOTP secret for generating one-time passwords.
+ totp_algorithm: Literal["SHA1", "SHA256", "SHA512"]
+ """HMAC algorithm used to generate TOTP codes.
+
+ Defaults to SHA1 and is ignored when an `otpauth://` URI supplies the algorithm.
+ """
+
+ totp_digits: int
+ """Number of digits in generated TOTP codes.
+
+ Defaults to 6 and is ignored when an `otpauth://` URI supplies the digit count.
+ """
- Used for automatic 2FA during login.
+ totp_period: int
+ """TOTP rotation period in seconds.
+
+ Defaults to 30 and is ignored when an `otpauth://` URI supplies the period.
+ """
+
+ totp_secret: str
+ """
+ Accepts a 16-128 character base32-encoded TOTP secret or an `otpauth://totp/...`
+ URI. The range accepts existing shorter seeds and longer seeds regardless of
+ HMAC algorithm; RFC 6238 recommends unpadded base32 lengths of 32/52/103 for
+ SHA1/SHA256/SHA512. Only URI parameters present override the corresponding
+ explicit TOTP fields. Used for automatic 2FA during login.
"""
diff --git a/src/kernel/types/credential_list_params.py b/src/kernel/types/credential_list_params.py
index 945909e5..ecaf2ef1 100644
--- a/src/kernel/types/credential_list_params.py
+++ b/src/kernel/types/credential_list_params.py
@@ -16,3 +16,9 @@ class CredentialListParams(TypedDict, total=False):
offset: int
"""Number of results to skip"""
+
+ query: str
+ """Case-insensitive substring match against credential name or domain.
+
+ IDs match by exact value.
+ """
diff --git a/src/kernel/types/credential_provider_create_params.py b/src/kernel/types/credential_provider_create_params.py
index ea531c7d..7076ff72 100644
--- a/src/kernel/types/credential_provider_create_params.py
+++ b/src/kernel/types/credential_provider_create_params.py
@@ -12,7 +12,10 @@ class CredentialProviderCreateParams(TypedDict, total=False):
"""Service account token for the provider (e.g., 1Password service account token)"""
name: Required[str]
- """Human-readable name for this provider instance (unique per org)"""
+ """Human-readable name for this provider instance (unique per org).
+
+ Surrounding whitespace is trimmed and the trimmed value must be non-empty.
+ """
provider_type: Required[Literal["onepassword"]]
"""Type of credential provider"""
diff --git a/src/kernel/types/credential_provider_list_params.py b/src/kernel/types/credential_provider_list_params.py
new file mode 100644
index 00000000..97571aa8
--- /dev/null
+++ b/src/kernel/types/credential_provider_list_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["CredentialProviderListParams"]
+
+
+class CredentialProviderListParams(TypedDict, total=False):
+ limit: int
+ """Limit the number of credential providers to return."""
+
+ offset: int
+ """Offset the number of credential providers to return."""
+
+ query: str
+ """Case-insensitive substring match against credential provider name.
+
+ IDs match by exact value.
+ """
diff --git a/src/kernel/types/credential_provider_list_response.py b/src/kernel/types/credential_provider_list_response.py
deleted file mode 100644
index 59814e03..00000000
--- a/src/kernel/types/credential_provider_list_response.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List
-from typing_extensions import TypeAlias
-
-from .credential_provider import CredentialProvider
-
-__all__ = ["CredentialProviderListResponse"]
-
-CredentialProviderListResponse: TypeAlias = List[CredentialProvider]
diff --git a/src/kernel/types/credential_provider_update_params.py b/src/kernel/types/credential_provider_update_params.py
index b2dda02b..272a7661 100644
--- a/src/kernel/types/credential_provider_update_params.py
+++ b/src/kernel/types/credential_provider_update_params.py
@@ -18,7 +18,10 @@ class CredentialProviderUpdateParams(TypedDict, total=False):
"""Whether the provider is enabled for credential lookups"""
name: str
- """Human-readable name for this provider instance"""
+ """Human-readable name for this provider instance.
+
+ Surrounding whitespace is trimmed and the trimmed value must be non-empty.
+ """
priority: int
"""Priority order for credential lookups (lower numbers are checked first)"""
diff --git a/src/kernel/types/credential_update_params.py b/src/kernel/types/credential_update_params.py
index c42209e4..976c62c4 100644
--- a/src/kernel/types/credential_update_params.py
+++ b/src/kernel/types/credential_update_params.py
@@ -3,7 +3,9 @@
from __future__ import annotations
from typing import Dict, Optional
-from typing_extensions import TypedDict
+from typing_extensions import Literal, TypedDict
+
+from .._types import SequenceNotStr
__all__ = ["CredentialUpdateParams"]
@@ -12,6 +14,13 @@ class CredentialUpdateParams(TypedDict, total=False):
name: str
"""New name for the credential"""
+ remove_value_keys: SequenceNotStr[str]
+ """Field names to remove from the credential's stored values.
+
+ Removals are applied before `values` are merged, so a key present in both is
+ kept with its new value.
+ """
+
sso_provider: Optional[str]
"""If set, indicates this credential should be used with the specified SSO
provider.
@@ -19,11 +28,34 @@ class CredentialUpdateParams(TypedDict, total=False):
Set to empty string or null to remove.
"""
- totp_secret: str
- """Base32-encoded TOTP secret for generating one-time passwords.
+ totp_algorithm: Literal["SHA1", "SHA256", "SHA512"]
+ """HMAC algorithm used to generate TOTP codes.
+
+ Requires totp_secret and is ignored when an `otpauth://` URI supplies the
+ algorithm.
+ """
+
+ totp_digits: int
+ """Number of digits in generated TOTP codes.
- Spaces and formatting are automatically normalized. Set to empty string to
- remove.
+ Requires totp_secret and is ignored when an `otpauth://` URI supplies the digit
+ count.
+ """
+
+ totp_period: int
+ """TOTP rotation period in seconds.
+
+ Requires totp_secret and is ignored when an `otpauth://` URI supplies the
+ period.
+ """
+
+ totp_secret: str
+ """
+ Accepts a 16-128 character base32-encoded TOTP secret or an `otpauth://totp/...`
+ URI. Only URI parameters present override the corresponding explicit TOTP
+ fields. When rotating a raw secret, omitted fields preserve their existing
+ values; a new URI defaults unspecified fields to SHA1/6/30. Set to empty string
+ to remove the secret and its metadata.
"""
values: Dict[str, str]
diff --git a/src/kernel/types/deployment_create_response.py b/src/kernel/types/deployment_create_response.py
index 5746c97b..439ed52a 100644
--- a/src/kernel/types/deployment_create_response.py
+++ b/src/kernel/types/deployment_create_response.py
@@ -28,7 +28,45 @@ class DeploymentCreateResponse(BaseModel):
"""Relative path to the application entrypoint"""
env_vars: Optional[Dict[str, str]] = None
- """Environment variables configured for this deployment"""
+ """Environment variables configured for this deployment.
+
+ Values are redacted for API key, OAuth, and managed-auth callers, which receive
+ every key with an empty string value. Only dashboard sessions receive the actual
+ values.
+ """
+
+ source_checksum: Optional[str] = None
+ """Hex-encoded SHA-256 checksum of the source archive.
+
+ For file uploads, this hashes the uploaded archive; for GitHub-sourced
+ deployments, this hashes the GitHub archive downloaded by the API. Omitted for
+ deployments created before this field was recorded.
+ """
+
+ source_path: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the subpath within the repository that was used
+ as the deploy root. Omitted when the repo root was used or for file uploads.
+ """
+
+ source_ref: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the git ref as requested at deploy time (branch,
+ tag, or commit SHA — not resolved to a commit). Omitted for file uploads.
+ """
+
+ source_type: Optional[Literal["file", "github"]] = None
+ """Origin of the deployed source code.
+
+ This is read-only response provenance; `file` indicates an uploaded archive and
+ `github` indicates a repository fetched by the API.
+ """
+
+ source_url: Optional[str] = None
+ """For GitHub-sourced deployments, the repository URL that was fetched.
+
+ Omitted for file uploads.
+ """
status_reason: Optional[str] = None
"""Status reason"""
diff --git a/src/kernel/types/deployment_follow_response.py b/src/kernel/types/deployment_follow_response.py
index d6de2223..36087a67 100644
--- a/src/kernel/types/deployment_follow_response.py
+++ b/src/kernel/types/deployment_follow_response.py
@@ -40,7 +40,10 @@ class AppVersionSummaryEvent(BaseModel):
"""Version label for the application"""
env_vars: Optional[Dict[str, str]] = None
- """Environment variables configured for this app version"""
+ """Environment variables configured for this app version.
+
+ Not currently populated on streamed app_version_summary events.
+ """
DeploymentFollowResponse: TypeAlias = Annotated[
diff --git a/src/kernel/types/deployment_list_params.py b/src/kernel/types/deployment_list_params.py
index 4c1c0717..7ee96dac 100644
--- a/src/kernel/types/deployment_list_params.py
+++ b/src/kernel/types/deployment_list_params.py
@@ -19,3 +19,6 @@ class DeploymentListParams(TypedDict, total=False):
offset: int
"""Offset the number of deployments to return."""
+
+ query: str
+ """Search deployments by ID or app name."""
diff --git a/src/kernel/types/deployment_list_response.py b/src/kernel/types/deployment_list_response.py
index d7719d48..accd0d05 100644
--- a/src/kernel/types/deployment_list_response.py
+++ b/src/kernel/types/deployment_list_response.py
@@ -28,7 +28,45 @@ class DeploymentListResponse(BaseModel):
"""Relative path to the application entrypoint"""
env_vars: Optional[Dict[str, str]] = None
- """Environment variables configured for this deployment"""
+ """Environment variables configured for this deployment.
+
+ Values are redacted for API key, OAuth, and managed-auth callers, which receive
+ every key with an empty string value. Only dashboard sessions receive the actual
+ values.
+ """
+
+ source_checksum: Optional[str] = None
+ """Hex-encoded SHA-256 checksum of the source archive.
+
+ For file uploads, this hashes the uploaded archive; for GitHub-sourced
+ deployments, this hashes the GitHub archive downloaded by the API. Omitted for
+ deployments created before this field was recorded.
+ """
+
+ source_path: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the subpath within the repository that was used
+ as the deploy root. Omitted when the repo root was used or for file uploads.
+ """
+
+ source_ref: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the git ref as requested at deploy time (branch,
+ tag, or commit SHA — not resolved to a commit). Omitted for file uploads.
+ """
+
+ source_type: Optional[Literal["file", "github"]] = None
+ """Origin of the deployed source code.
+
+ This is read-only response provenance; `file` indicates an uploaded archive and
+ `github` indicates a repository fetched by the API.
+ """
+
+ source_url: Optional[str] = None
+ """For GitHub-sourced deployments, the repository URL that was fetched.
+
+ Omitted for file uploads.
+ """
status_reason: Optional[str] = None
"""Status reason"""
diff --git a/src/kernel/types/deployment_retrieve_response.py b/src/kernel/types/deployment_retrieve_response.py
index 3601c864..2f4cf49e 100644
--- a/src/kernel/types/deployment_retrieve_response.py
+++ b/src/kernel/types/deployment_retrieve_response.py
@@ -28,7 +28,45 @@ class DeploymentRetrieveResponse(BaseModel):
"""Relative path to the application entrypoint"""
env_vars: Optional[Dict[str, str]] = None
- """Environment variables configured for this deployment"""
+ """Environment variables configured for this deployment.
+
+ Values are redacted for API key, OAuth, and managed-auth callers, which receive
+ every key with an empty string value. Only dashboard sessions receive the actual
+ values.
+ """
+
+ source_checksum: Optional[str] = None
+ """Hex-encoded SHA-256 checksum of the source archive.
+
+ For file uploads, this hashes the uploaded archive; for GitHub-sourced
+ deployments, this hashes the GitHub archive downloaded by the API. Omitted for
+ deployments created before this field was recorded.
+ """
+
+ source_path: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the subpath within the repository that was used
+ as the deploy root. Omitted when the repo root was used or for file uploads.
+ """
+
+ source_ref: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the git ref as requested at deploy time (branch,
+ tag, or commit SHA — not resolved to a commit). Omitted for file uploads.
+ """
+
+ source_type: Optional[Literal["file", "github"]] = None
+ """Origin of the deployed source code.
+
+ This is read-only response provenance; `file` indicates an uploaded archive and
+ `github` indicates a repository fetched by the API.
+ """
+
+ source_url: Optional[str] = None
+ """For GitHub-sourced deployments, the repository URL that was fetched.
+
+ Omitted for file uploads.
+ """
status_reason: Optional[str] = None
"""Status reason"""
diff --git a/src/kernel/types/deployment_state_event.py b/src/kernel/types/deployment_state_event.py
index cc221c77..64835725 100644
--- a/src/kernel/types/deployment_state_event.py
+++ b/src/kernel/types/deployment_state_event.py
@@ -28,7 +28,45 @@ class Deployment(BaseModel):
"""Relative path to the application entrypoint"""
env_vars: Optional[Dict[str, str]] = None
- """Environment variables configured for this deployment"""
+ """Environment variables configured for this deployment.
+
+ Values are redacted for API key, OAuth, and managed-auth callers, which receive
+ every key with an empty string value. Only dashboard sessions receive the actual
+ values.
+ """
+
+ source_checksum: Optional[str] = None
+ """Hex-encoded SHA-256 checksum of the source archive.
+
+ For file uploads, this hashes the uploaded archive; for GitHub-sourced
+ deployments, this hashes the GitHub archive downloaded by the API. Omitted for
+ deployments created before this field was recorded.
+ """
+
+ source_path: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the subpath within the repository that was used
+ as the deploy root. Omitted when the repo root was used or for file uploads.
+ """
+
+ source_ref: Optional[str] = None
+ """
+ For GitHub-sourced deployments, the git ref as requested at deploy time (branch,
+ tag, or commit SHA — not resolved to a commit). Omitted for file uploads.
+ """
+
+ source_type: Optional[Literal["file", "github"]] = None
+ """Origin of the deployed source code.
+
+ This is read-only response provenance; `file` indicates an uploaded archive and
+ `github` indicates a repository fetched by the API.
+ """
+
+ source_url: Optional[str] = None
+ """For GitHub-sourced deployments, the repository URL that was fetched.
+
+ Omitted for file uploads.
+ """
status_reason: Optional[str] = None
"""Status reason"""
diff --git a/src/kernel/types/evidence.py b/src/kernel/types/evidence.py
new file mode 100644
index 00000000..c53f2842
--- /dev/null
+++ b/src/kernel/types/evidence.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["Evidence"]
+
+
+class Evidence(BaseModel):
+ accessed: int
+
+ blocked: int
+
+ inconclusive: int
+
+ last_observed_at: datetime
+ """Most recent contributing observation.
+
+ Recommendations remain eligible regardless of age and can be returned while a
+ new analysis refreshes them.
+ """
+
+ run_count: int
+
+ sample_size: int
+ """Number of judged trials."""
+
+ success_rate: float
+ """Accessed trials divided by judged trials. Inconclusive trials are excluded."""
+
+ last_supported_at: Optional[datetime] = None
+ """
+ Most recent contributing run whose evidence supported recommending this
+ configuration. Omitted when no individual run independently met the
+ recommendation threshold.
+ """
diff --git a/src/kernel/types/extension_get_response.py b/src/kernel/types/extension_get_response.py
new file mode 100644
index 00000000..a4d10b1f
--- /dev/null
+++ b/src/kernel/types/extension_get_response.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["ExtensionGetResponse"]
+
+
+class ExtensionGetResponse(BaseModel):
+ """A browser extension uploaded to Kernel."""
+
+ id: str
+ """Unique identifier for the extension"""
+
+ created_at: datetime
+ """Timestamp when the extension was created"""
+
+ size_bytes: int
+ """Size of the extension archive in bytes"""
+
+ checksum: Optional[str] = None
+ """
+ SHA-256 checksum, encoded as lowercase hexadecimal, of the exact uploaded
+ extension archive bytes. This is not a normalized checksum of the extension
+ contents; archive metadata, file ordering, and compression can change the
+ checksum for otherwise identical contents. Omitted for legacy rows and
+ server-repackaged Chrome Web Store extensions.
+ """
+
+ last_used_at: Optional[datetime] = None
+ """Timestamp when the extension was last used"""
+
+ name: Optional[str] = None
+ """Optional, easier-to-reference name for the extension.
+
+ Must be unique within the project.
+ """
diff --git a/src/kernel/types/extension_list_params.py b/src/kernel/types/extension_list_params.py
new file mode 100644
index 00000000..4c6669a9
--- /dev/null
+++ b/src/kernel/types/extension_list_params.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ExtensionListParams"]
+
+
+class ExtensionListParams(TypedDict, total=False):
+ limit: int
+ """Limit the number of extensions to return."""
+
+ name: str
+ """Exact-match filter on extension name using the database collation.
+
+ In production, matching is case- and accent-insensitive. During the
+ default-project migration, unscoped requests prefer a concrete default-project
+ extension over a legacy unscoped extension with the same name.
+ """
+
+ offset: int
+ """Offset the number of extensions to return."""
+
+ query: str
+ """Case-insensitive substring match against extension name.
+
+ IDs match by exact value.
+ """
diff --git a/src/kernel/types/extension_list_response.py b/src/kernel/types/extension_list_response.py
index bf9e544d..d7d0ea0d 100644
--- a/src/kernel/types/extension_list_response.py
+++ b/src/kernel/types/extension_list_response.py
@@ -1,15 +1,14 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import Optional
from datetime import datetime
-from typing_extensions import TypeAlias
from .._models import BaseModel
-__all__ = ["ExtensionListResponse", "ExtensionListResponseItem"]
+__all__ = ["ExtensionListResponse"]
-class ExtensionListResponseItem(BaseModel):
+class ExtensionListResponse(BaseModel):
"""A browser extension uploaded to Kernel."""
id: str
@@ -21,6 +20,15 @@ class ExtensionListResponseItem(BaseModel):
size_bytes: int
"""Size of the extension archive in bytes"""
+ checksum: Optional[str] = None
+ """
+ SHA-256 checksum, encoded as lowercase hexadecimal, of the exact uploaded
+ extension archive bytes. This is not a normalized checksum of the extension
+ contents; archive metadata, file ordering, and compression can change the
+ checksum for otherwise identical contents. Omitted for legacy rows and
+ server-repackaged Chrome Web Store extensions.
+ """
+
last_used_at: Optional[datetime] = None
"""Timestamp when the extension was last used"""
@@ -29,6 +37,3 @@ class ExtensionListResponseItem(BaseModel):
Must be unique within the project.
"""
-
-
-ExtensionListResponse: TypeAlias = List[ExtensionListResponseItem]
diff --git a/src/kernel/types/extension_upload_response.py b/src/kernel/types/extension_upload_response.py
index 068b25dd..b44390d4 100644
--- a/src/kernel/types/extension_upload_response.py
+++ b/src/kernel/types/extension_upload_response.py
@@ -20,6 +20,15 @@ class ExtensionUploadResponse(BaseModel):
size_bytes: int
"""Size of the extension archive in bytes"""
+ checksum: Optional[str] = None
+ """
+ SHA-256 checksum, encoded as lowercase hexadecimal, of the exact uploaded
+ extension archive bytes. This is not a normalized checksum of the extension
+ contents; archive metadata, file ordering, and compression can change the
+ checksum for otherwise identical contents. Omitted for legacy rows and
+ server-repackaged Chrome Web Store extensions.
+ """
+
last_used_at: Optional[datetime] = None
"""Timestamp when the extension was last used"""
diff --git a/src/kernel/types/invocation_create_response.py b/src/kernel/types/invocation_create_response.py
index 21fbcf33..3e775279 100644
--- a/src/kernel/types/invocation_create_response.py
+++ b/src/kernel/types/invocation_create_response.py
@@ -19,10 +19,18 @@ class InvocationCreateResponse(BaseModel):
"""Status of the invocation"""
output: Optional[str] = None
- """The return value of the action that was invoked, rendered as a JSON string.
+ """The action result or detailed failure output.
- This could be: string, number, boolean, array, object, or null.
+ Often a JSON-encoded value, but failures may contain plain text. May contain
+ sensitive application data.
"""
status_reason: Optional[str] = None
- """Status reason"""
+ """
+ A nonempty, customer-safe summary of the recorded failure output, always present
+ when status is failed and omitted otherwise. Recognized messages receive a
+ specific summary; other failures receive a generic summary. Message matching
+ does not establish whether the failure originated in the platform or action
+ code. Does not include raw action output or internal error details.
+ Human-readable text, not a stable identifier for retry logic.
+ """
diff --git a/src/kernel/types/invocation_list_browsers_response.py b/src/kernel/types/invocation_list_browsers_response.py
index 71c22a7a..2df9877c 100644
--- a/src/kernel/types/invocation_list_browsers_response.py
+++ b/src/kernel/types/invocation_list_browsers_response.py
@@ -1,14 +1,22 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import Dict, List, Optional
from datetime import datetime
+from typing_extensions import Literal
+from .tags import Tags
from .profile import Profile
from .._models import BaseModel
+from .browser_proxy import BrowserProxy
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_persistence import BrowserPersistence
+from .browser_video_memory import BrowserVideoMemory
+from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
+from .browsers.browser_telemetry_config import BrowserTelemetryConfig
__all__ = ["InvocationListBrowsersResponse", "Browser"]
@@ -23,6 +31,12 @@ class Browser(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
+ """Geographic region of the browser session. Fixed once the session is created."""
+
session_id: str
"""Unique identifier for the browser session"""
@@ -44,6 +58,13 @@ class Browser(BaseModel):
Only available for non-headless browsers.
"""
+ chrome_policy: Optional[Dict[str, object]] = None
+ """
+ Custom Chrome enterprise policy overrides that were applied to this browser
+ session, if any. Echoed back for verification. Keys are Chrome enterprise policy
+ names.
+ """
+
deleted_at: Optional[datetime] = None
"""When the browser session was soft-deleted. Only present for deleted sessions."""
@@ -56,8 +77,17 @@ class Browser(BaseModel):
kiosk_mode: Optional[bool] = None
"""Whether the browser session is running in kiosk mode."""
- persistence: Optional[BrowserPersistence] = None
- """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+ 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."""
+
+ network: Optional[BrowserNetworkConfig] = None
+ """Network configuration the session was created with, if any.
+
+ Omitted when the session has no network configuration.
+ """
pool: Optional[BrowserPoolRef] = None
"""Browser pool this session was acquired from, if any."""
@@ -65,32 +95,68 @@ class Browser(BaseModel):
profile: Optional[Profile] = None
"""Browser profile metadata."""
+ profile_save_changes: Optional[bool] = None
+ """
+ Whether changes made during this browser session are saved back to its profile
+ when the session ends. Omitted when no profile is attached.
+ """
+
+ proxy: Optional[BrowserProxy] = None
+ """Resolved proxy configuration for this browser session."""
+
proxy_id: Optional[str] = None
- """ID of the proxy associated with this browser session, if any."""
+ """ID of the proxy associated with this browser session, if any.
+
+ Deprecated in favor of proxy.
+ """
start_url: Optional[str] = None
- """URL the session was asked to navigate to on creation, if any.
+ """URL the session was most recently asked to navigate to, if any.
+
+ Recorded for debugging. Navigation is fire-and-forget — the URL is dispatched to
+ the browser without waiting for it to load, and any errors (DNS failure, bad
+ status, timeout) are silently dropped. Captures what was requested, not what the
+ browser actually loaded.
+ """
+
+ tags: Optional[Tags] = None
+ """User-defined key-value tags that were set on this browser session, if any.
- Recorded for debugging — navigation is best-effort and may have failed.
+ Echoed back when present.
"""
+ telemetry: Optional[BrowserTelemetryConfig] = None
+ """Active telemetry configuration for the session, if any."""
+
usage: Optional[BrowserUsage] = None
"""Session usage metrics."""
+ usage_status: Optional[Literal["pending", "ready"]] = None
+ """Whether final usage billing is still pending or complete.
+
+ Only present for deleted sessions.
+ """
+
+ vaults: Optional[List[VaultReference]] = None
+ """Vaults linked when the browser session was created."""
+
+ video_memory: Optional[BrowserVideoMemory] = None
+ """Video memory (VRAM) of the GPU browser session. Only present when gpu is true."""
+
viewport: Optional[BrowserViewport] = None
"""
Initial browser window size in pixels with optional refresh rate. If omitted,
image defaults apply (1920x1080@25). For GPU images, the default is
1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
- 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
- presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
- 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
- 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
- Viewports outside this list may exhibit unstable live view or recording
- behavior. If refresh_rate is not provided, it will be automatically determined
- based on the resolution (higher resolutions use lower refresh rates to keep
- bandwidth reasonable).
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60. For
+ GPU images, recommended presets use one of these resolutions with refresh rates
+ 60, 30, 25, or 10: 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800,
+ 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250,
+ 768x1024, 800x1600. Viewports outside this list may exhibit unstable live view
+ or recording behavior. If refresh_rate is not provided, it will be automatically
+ determined based on the resolution (higher resolutions use lower refresh rates
+ to keep bandwidth reasonable).
"""
diff --git a/src/kernel/types/invocation_list_params.py b/src/kernel/types/invocation_list_params.py
index 9673f2d6..2bcfe9c7 100644
--- a/src/kernel/types/invocation_list_params.py
+++ b/src/kernel/types/invocation_list_params.py
@@ -23,6 +23,9 @@ class InvocationListParams(TypedDict, total=False):
offset: int
"""Offset the number of invocations to return."""
+ query: str
+ """Search invocations by ID, app name, or action name."""
+
since: str
"""
Show invocations that have started since the given time (RFC timestamps or
diff --git a/src/kernel/types/invocation_list_response.py b/src/kernel/types/invocation_list_response.py
index e635b4d8..f55dff9c 100644
--- a/src/kernel/types/invocation_list_response.py
+++ b/src/kernel/types/invocation_list_response.py
@@ -35,9 +35,10 @@ class InvocationListResponse(BaseModel):
"""
output: Optional[str] = None
- """Output produced by the action, rendered as a JSON string.
+ """The action result or detailed failure output.
- This could be: string, number, boolean, array, object, or null.
+ Often a JSON-encoded value, but failures may contain plain text. May contain
+ sensitive application data.
"""
payload: Optional[str] = None
@@ -47,4 +48,12 @@ class InvocationListResponse(BaseModel):
"""
status_reason: Optional[str] = None
- """Status reason"""
+ """
+ A nonempty, customer-safe summary of the recorded failure output, always present
+ when status is failed and omitted otherwise, including in the first failed
+ invocation_state event. Recognized messages receive a specific summary; other
+ failures receive a generic summary. Message matching does not establish whether
+ the failure originated in the platform or action code. Does not include raw
+ action output or internal error details. Available for historical invocations as
+ well. Human-readable text, not a stable identifier for retry logic.
+ """
diff --git a/src/kernel/types/invocation_retrieve_response.py b/src/kernel/types/invocation_retrieve_response.py
index 580424eb..e16c02de 100644
--- a/src/kernel/types/invocation_retrieve_response.py
+++ b/src/kernel/types/invocation_retrieve_response.py
@@ -35,9 +35,10 @@ class InvocationRetrieveResponse(BaseModel):
"""
output: Optional[str] = None
- """Output produced by the action, rendered as a JSON string.
+ """The action result or detailed failure output.
- This could be: string, number, boolean, array, object, or null.
+ Often a JSON-encoded value, but failures may contain plain text. May contain
+ sensitive application data.
"""
payload: Optional[str] = None
@@ -47,4 +48,12 @@ class InvocationRetrieveResponse(BaseModel):
"""
status_reason: Optional[str] = None
- """Status reason"""
+ """
+ A nonempty, customer-safe summary of the recorded failure output, always present
+ when status is failed and omitted otherwise, including in the first failed
+ invocation_state event. Recognized messages receive a specific summary; other
+ failures receive a generic summary. Message matching does not establish whether
+ the failure originated in the platform or action code. Does not include raw
+ action output or internal error details. Available for historical invocations as
+ well. Human-readable text, not a stable identifier for retry logic.
+ """
diff --git a/src/kernel/types/invocation_state_event.py b/src/kernel/types/invocation_state_event.py
index f32bf8e9..00abae7d 100644
--- a/src/kernel/types/invocation_state_event.py
+++ b/src/kernel/types/invocation_state_event.py
@@ -35,9 +35,10 @@ class Invocation(BaseModel):
"""
output: Optional[str] = None
- """Output produced by the action, rendered as a JSON string.
+ """The action result or detailed failure output.
- This could be: string, number, boolean, array, object, or null.
+ Often a JSON-encoded value, but failures may contain plain text. May contain
+ sensitive application data.
"""
payload: Optional[str] = None
@@ -47,7 +48,15 @@ class Invocation(BaseModel):
"""
status_reason: Optional[str] = None
- """Status reason"""
+ """
+ A nonempty, customer-safe summary of the recorded failure output, always present
+ when status is failed and omitted otherwise, including in the first failed
+ invocation_state event. Recognized messages receive a specific summary; other
+ failures receive a generic summary. Message matching does not establish whether
+ the failure originated in the platform or action code. Does not include raw
+ action output or internal error details. Available for historical invocations as
+ well. Human-readable text, not a stable identifier for retry logic.
+ """
class InvocationStateEvent(BaseModel):
diff --git a/src/kernel/types/invocation_update_response.py b/src/kernel/types/invocation_update_response.py
index 3bcc8bc0..92dfe5ed 100644
--- a/src/kernel/types/invocation_update_response.py
+++ b/src/kernel/types/invocation_update_response.py
@@ -35,9 +35,10 @@ class InvocationUpdateResponse(BaseModel):
"""
output: Optional[str] = None
- """Output produced by the action, rendered as a JSON string.
+ """The action result or detailed failure output.
- This could be: string, number, boolean, array, object, or null.
+ Often a JSON-encoded value, but failures may contain plain text. May contain
+ sensitive application data.
"""
payload: Optional[str] = None
@@ -47,4 +48,12 @@ class InvocationUpdateResponse(BaseModel):
"""
status_reason: Optional[str] = None
- """Status reason"""
+ """
+ A nonempty, customer-safe summary of the recorded failure output, always present
+ when status is failed and omitted otherwise, including in the first failed
+ invocation_state event. Recognized messages receive a specific summary; other
+ failures receive a generic summary. Message matching does not establish whether
+ the failure originated in the platform or action code. Does not include raw
+ action output or internal error details. Available for historical invocations as
+ well. Human-readable text, not a stable identifier for retry logic.
+ """
diff --git a/src/kernel/types/lookup_response.py b/src/kernel/types/lookup_response.py
new file mode 100644
index 00000000..c92e163e
--- /dev/null
+++ b/src/kernel/types/lookup_response.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .target import Target
+from .._models import BaseModel
+from .recommendation import Recommendation
+from .recommendation_result import RecommendationResult
+
+__all__ = ["LookupResponse"]
+
+
+class LookupResponse(BaseModel):
+ recommendation: Optional[RecommendationResult] = None
+ """A recommendation or a structured no-recommendation result."""
+
+ target: Target
+
+ working_configurations: List[Recommendation]
+ """
+ Working configurations for the target, ordered with the recommended
+ configuration first.
+ """
+
+ guidance: Optional[str] = None
+ """Short advisory markdown to facilitate navigating this target.
+
+ Returned even when no configuration reached the target, since knowing what
+ prevented success is useful without a configuration. Not verified against this
+ target. Null when nothing applicable was observed or no notes exist.
+ """
diff --git a/src/kernel/types/no_recommendation.py b/src/kernel/types/no_recommendation.py
new file mode 100644
index 00000000..40edb9f0
--- /dev/null
+++ b/src/kernel/types/no_recommendation.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["NoRecommendation"]
+
+
+class NoRecommendation(BaseModel):
+ code: Literal["proxy_restricted", "target_not_evaluable", "no_working_configuration", "inconclusive"]
+ """
+ Machine-readable reason Kernel cannot currently provide a config recommendation.
+ """
+
+ message: str
+ """Human-readable explanation suitable for display."""
+
+ type: Literal["no_recommendation"]
diff --git a/src/kernel/types/organization/__init__.py b/src/kernel/types/organization/__init__.py
new file mode 100644
index 00000000..415d132a
--- /dev/null
+++ b/src/kernel/types/organization/__init__.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .org_limits import OrgLimits as OrgLimits
+from .org_entitlements import OrgEntitlements as OrgEntitlements
+from .limit_update_params import LimitUpdateParams as LimitUpdateParams
diff --git a/src/kernel/types/organization/limit_update_params.py b/src/kernel/types/organization/limit_update_params.py
new file mode 100644
index 00000000..4c85f9c1
--- /dev/null
+++ b/src/kernel/types/organization/limit_update_params.py
@@ -0,0 +1,17 @@
+# 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):
+ default_project_max_concurrent_sessions: Optional[int]
+ """Default maximum concurrent browsers for projects without an explicit override.
+
+ Set to 0 to remove the default; omit to leave unchanged. Cannot exceed the
+ organization's concurrency limit.
+ """
diff --git a/src/kernel/types/organization/org_entitlements.py b/src/kernel/types/organization/org_entitlements.py
new file mode 100644
index 00000000..fd871249
--- /dev/null
+++ b/src/kernel/types/organization/org_entitlements.py
@@ -0,0 +1,237 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = [
+ "OrgEntitlements",
+ "Features",
+ "FeaturesBrowserExtensions",
+ "FeaturesBrowserPools",
+ "FeaturesBrowserReplays",
+ "FeaturesCredentialProviders",
+ "FeaturesCredentials",
+ "FeaturesCustomProxies",
+ "FeaturesFileIo",
+ "FeaturesGPU",
+ "FeaturesManagedAuth",
+ "FeaturesManagedProxies",
+ "FeaturesProfiles",
+ "FeaturesProxyBypassHosts",
+ "FeaturesSearch",
+ "FeaturesVaults",
+ "Limits",
+ "Plan",
+]
+
+
+class FeaturesBrowserExtensions(BaseModel):
+ enabled: bool
+ """Whether browser extensions are available."""
+
+ max_stored_per_org: Optional[int] = None
+ """Maximum active custom extensions the organization may store.
+
+ Null means unlimited. Loading stored extensions into a browser is not
+ plan-limited.
+ """
+
+
+class FeaturesBrowserPools(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesBrowserReplays(BaseModel):
+ enabled: bool
+ """Whether browser replay recording is available."""
+
+ retention_days: int
+ """Number of days browser replays are retained, matching the replay reaper policy."""
+
+
+class FeaturesCredentialProviders(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesCredentials(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesCustomProxies(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesFileIo(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesGPU(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesManagedAuth(BaseModel):
+ enabled: bool
+ """Whether managed auth is available."""
+
+ health_check_interval_default_seconds: int
+ """
+ Effective interval in seconds used when a connection is created without an
+ explicit health-check interval.
+ """
+
+ health_check_interval_max_seconds: int
+ """Maximum accepted managed auth health-check interval in seconds."""
+
+ health_check_interval_min_seconds: int
+ """Minimum accepted managed auth health-check interval in seconds."""
+
+ max_connections: Optional[int] = None
+ """Maximum active managed auth connections in the organization.
+
+ Null means unlimited.
+ """
+
+
+class FeaturesManagedProxies(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesProfiles(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesProxyBypassHosts(BaseModel):
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesSearch(BaseModel):
+ """
+ Whether the Search API is enabled for the organization by its rollout feature flag.
+ """
+
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class FeaturesVaults(BaseModel):
+ """
+ Whether the organization can access vaults, using the same access check as vault API routes.
+ """
+
+ enabled: bool
+ """Whether the organization is entitled to use this feature."""
+
+
+class Features(BaseModel):
+ browser_extensions: FeaturesBrowserExtensions
+
+ browser_pools: FeaturesBrowserPools
+
+ browser_replays: FeaturesBrowserReplays
+
+ credential_providers: FeaturesCredentialProviders
+
+ credentials: FeaturesCredentials
+
+ custom_proxies: FeaturesCustomProxies
+
+ file_io: FeaturesFileIo
+
+ gpu: FeaturesGPU
+
+ managed_auth: FeaturesManagedAuth
+
+ managed_proxies: FeaturesManagedProxies
+
+ profiles: FeaturesProfiles
+
+ proxy_bypass_hosts: FeaturesProxyBypassHosts
+
+ search: FeaturesSearch
+ """
+ Whether the Search API is enabled for the organization by its rollout feature
+ flag.
+ """
+
+ vaults: FeaturesVaults
+ """
+ Whether the organization can access vaults, using the same access check as vault
+ API routes.
+ """
+
+
+class Limits(BaseModel):
+ default_max_concurrent_invocations_per_app: int
+ """
+ Effective org-level default concurrent invocation ceiling for apps without an
+ app-specific override. App-specific overrides are not represented here.
+ """
+
+ max_concurrent_browsers: int
+ """
+ Effective organization-wide ceiling shared by on-demand browsers and browser
+ pool reservations.
+ """
+
+ max_concurrent_invocations: int
+ """Effective organization-wide concurrent app invocation ceiling."""
+
+ max_vaults: Optional[int] = None
+ """Maximum non-deleted vaults allowed org-wide across all projects.
+
+ Null means unlimited. The vaults feature flag still controls access.
+ """
+
+
+class Plan(BaseModel):
+ id: Literal["FREE", "HOBBYIST", "START_UP", "ENTERPRISE"]
+ """The organization's contractual plan identifier.
+
+ Use the resolved feature and limit values, not this field, for access decisions.
+ """
+
+ effective_id: Literal["FREE", "HOBBYIST", "START_UP", "ENTERPRISE"]
+ """The plan used to resolve plan-based access.
+
+ Active trials resolve to START_UP regardless of the contractual plan.
+ """
+
+ is_trialing: bool
+ """Whether the organization is currently within its trial period."""
+
+ status: Optional[Literal["NEEDS_PAYMENT_METHOD", "ACTIVE", "CANCELED", "UNPAID"]] = None
+ """
+ Current billing status of the contractual plan, or null when no billing status
+ is recorded. Status-sensitive feature values already account for it.
+ """
+
+ trial_ends_at: Optional[datetime] = None
+ """Configured trial end timestamp, or null when the organization has no trial.
+
+ A past timestamp may be returned when is_trialing is false.
+ """
+
+
+class OrgEntitlements(BaseModel):
+ """Effective feature access and constraints for the authenticated organization.
+
+ Values already include trial treatment, plan status, and organization-specific overrides; consumers should use these resolved values instead of comparing plan IDs.
+ """
+
+ features: Features
+
+ limits: Limits
+
+ plan: Plan
diff --git a/src/kernel/types/organization/org_limits.py b/src/kernel/types/organization/org_limits.py
new file mode 100644
index 00000000..fbe26e76
--- /dev/null
+++ b/src/kernel/types/organization/org_limits.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["OrgLimits"]
+
+
+class OrgLimits(BaseModel):
+ auth_connections_used: int
+ """
+ The organization's current non-deleted managed auth connections, counted
+ org-wide across every project. Compare against max_auth_connections to show
+ remaining capacity before a create is rejected with 403 insufficient_plan.
+ """
+
+ concurrent_sessions_available: Optional[int] = None
+ """Number of concurrent browser slots currently available to the organization.
+
+ This is the effective concurrency limit minus active on-demand sessions and
+ browser pool reservations, floored at zero. Null when usage cannot be read.
+ """
+
+ concurrent_sessions_used: Optional[int] = None
+ """
+ Current organization-wide concurrent browser usage, including active on-demand
+ sessions and browser pool reservations. Null when usage cannot be read.
+ """
+
+ max_auth_connections: Optional[int] = None
+ """Maximum managed auth connections the organization's plan allows.
+
+ Null means unlimited. Counted org-wide, so it cannot be multiplied across
+ projects.
+ """
+
+ max_vaults: Optional[int] = None
+ """Maximum non-deleted vaults allowed org-wide across all projects.
+
+ Null means unlimited.
+ """
+
+ min_health_check_interval_seconds: int
+ """
+ Smallest health_check_interval the organization's plan accepts on a managed auth
+ connection. Requests below this are rejected with 400. Existing connections
+ stored below the floor are grandfathered until edited.
+ """
+
+ vaults_used: int
+ """Current non-deleted vault count across all projects in the organization."""
+
+ default_project_max_concurrent_sessions: Optional[int] = None
+ """
+ Default maximum concurrent browsers applied to every project that has no
+ explicit per-project override. Null means no org-level default, so such projects
+ are uncapped (only the org-wide limit applies). Applies to existing and newly
+ created projects.
+ """
+
+ max_concurrent_sessions: Optional[int] = None
+ """
+ The organization's effective concurrency limit — the maximum browsers running at
+ once, covering both on-demand sessions and browser pool reservations — from its
+ plan or an override. Read-only and shared across all projects in the org; a
+ per-project default cannot exceed it.
+ """
diff --git a/src/kernel/types/profile_create_params.py b/src/kernel/types/profile_create_params.py
index bca1e17d..8d1c2f4f 100644
--- a/src/kernel/types/profile_create_params.py
+++ b/src/kernel/types/profile_create_params.py
@@ -9,4 +9,9 @@
class ProfileCreateParams(TypedDict, total=False):
name: str
- """Optional name of the profile. Must be unique within the project."""
+ """Optional name of the profile.
+
+ Must be unique within the logical project; during the default-project migration,
+ unscoped profiles and profiles in the org default project are treated as the
+ same project.
+ """
diff --git a/src/kernel/types/profile_download_params.py b/src/kernel/types/profile_download_params.py
new file mode 100644
index 00000000..32bfa3dd
--- /dev/null
+++ b/src/kernel/types/profile_download_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__ = ["ProfileDownloadParams"]
+
+
+class ProfileDownloadParams(TypedDict, total=False):
+ format: Literal["tar.zst", "tar"]
+ """Response format for current profile archives.
+
+ Legacy profiles are always returned as JSON.
+ """
diff --git a/src/kernel/types/profile_list_params.py b/src/kernel/types/profile_list_params.py
index 4218f5bc..5a572f6b 100644
--- a/src/kernel/types/profile_list_params.py
+++ b/src/kernel/types/profile_list_params.py
@@ -11,8 +11,16 @@ class ProfileListParams(TypedDict, total=False):
limit: int
"""Limit the number of profiles to return."""
+ name: str
+ """Exact-match filter on profile name using the database collation.
+
+ In production, matching is case- and accent-insensitive. During the
+ default-project migration, unscoped requests prefer a concrete default-project
+ profile over a legacy unscoped profile with the same name.
+ """
+
offset: int
"""Offset the number of profiles to return."""
query: str
- """Search profiles by name or ID."""
+ """Case-insensitive substring match against profile name or ID."""
diff --git a/src/kernel/types/profile_update_params.py b/src/kernel/types/profile_update_params.py
new file mode 100644
index 00000000..bd0165b7
--- /dev/null
+++ b/src/kernel/types/profile_update_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ProfileUpdateParams"]
+
+
+class ProfileUpdateParams(TypedDict, total=False):
+ name: Required[str]
+ """New profile name.
+
+ Must be unique within the logical project; during the default-project migration,
+ unscoped profiles and profiles in the org default project are treated as the
+ same project.
+ """
diff --git a/src/kernel/types/project_create_params.py b/src/kernel/types/project_create_params.py
index 99a59864..664e89bc 100644
--- a/src/kernel/types/project_create_params.py
+++ b/src/kernel/types/project_create_params.py
@@ -9,4 +9,4 @@
class ProjectCreateParams(TypedDict, total=False):
name: Required[str]
- """Project name (1-255 characters)"""
+ """Project name (1-255 Unicode code points; cannot contain `/` or `%`)"""
diff --git a/src/kernel/types/project_list_params.py b/src/kernel/types/project_list_params.py
index 9e740e60..dd307d17 100644
--- a/src/kernel/types/project_list_params.py
+++ b/src/kernel/types/project_list_params.py
@@ -11,6 +11,12 @@ class ProjectListParams(TypedDict, total=False):
limit: int
"""Maximum number of results to return"""
+ name: str
+ """Exact-match filter on project name using the database collation.
+
+ In production, matching is case- and accent-insensitive.
+ """
+
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
index ea7de900..3d93cef4 100644
--- a/src/kernel/types/project_update_params.py
+++ b/src/kernel/types/project_update_params.py
@@ -9,7 +9,7 @@
class ProjectUpdateParams(TypedDict, total=False):
name: str
- """New project name"""
+ """New project name (1-255 Unicode code points; cannot contain `/` or `%`)"""
status: Literal["active", "archived"]
"""New project status"""
diff --git a/src/kernel/types/projects/limit_update_params.py b/src/kernel/types/projects/limit_update_params.py
index 6f0ec8ae..e8bd1b60 100644
--- a/src/kernel/types/projects/limit_update_params.py
+++ b/src/kernel/types/projects/limit_update_params.py
@@ -16,19 +16,14 @@ class LimitUpdateParams(TypedDict, total=False):
"""
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.
+ Maximum concurrent browsers for this project, covering both on-demand sessions
+ and browser pool reservations. Set to 0 to remove the cap; omit to leave
+ unchanged.
"""
max_pooled_sessions: Optional[int]
- """Maximum pooled sessions capacity for this project.
+ """Deprecated: pooled browsers now count toward `max_concurrent_sessions`.
- Set to 0 to remove the cap; omit to leave unchanged.
+ Requests that set this field are rejected with a 400.
"""
diff --git a/src/kernel/types/projects/project_limits.py b/src/kernel/types/projects/project_limits.py
index bf49b2a2..712e84c1 100644
--- a/src/kernel/types/projects/project_limits.py
+++ b/src/kernel/types/projects/project_limits.py
@@ -15,19 +15,14 @@ class ProjectLimits(BaseModel):
"""
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.
+ Maximum concurrent browsers for this project, covering both on-demand sessions
+ (`browsers.create()`) and browser pool reservations. Null means no project-level
+ cap.
"""
max_pooled_sessions: Optional[int] = None
- """Maximum pooled sessions capacity for this project.
+ """Deprecated: pooled browsers now count toward `max_concurrent_sessions`.
- Null means no project-level cap.
+ Always null.
"""
diff --git a/src/kernel/types/provider_target_param.py b/src/kernel/types/provider_target_param.py
new file mode 100644
index 00000000..4251880f
--- /dev/null
+++ b/src/kernel/types/provider_target_param.py
@@ -0,0 +1,642 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from datetime import date, datetime
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+
+__all__ = [
+ "ProviderTargetParam",
+ "SearchBraveTarget",
+ "SearchBraveTargetOptions",
+ "SearchExaTarget",
+ "SearchExaTargetOptions",
+ "SearchExaTargetOptionsContents",
+ "SearchPerplexityTarget",
+ "SearchPerplexityTargetOptions",
+ "SearchContextTarget",
+ "SearchContextTargetOptions",
+ "SearchContextTargetOptionsMarkdownOptions",
+ "SearchContextTargetOptionsMarkdownOptionsPdf",
+ "SearchParallelTarget",
+ "SearchParallelTargetOptions",
+ "SearchParallelTargetOptionsAdvancedSettings",
+ "SearchParallelTargetOptionsAdvancedSettingsExcerptSettings",
+ "SearchParallelTargetOptionsAdvancedSettingsFetchPolicy",
+ "SearchParallelTargetOptionsAdvancedSettingsSourcePolicy",
+ "SearchValyuTarget",
+ "SearchValyuTargetOptions",
+ "SearchOctenTarget",
+ "SearchOctenTargetOptions",
+ "SearchOctenTargetOptionsFullContent",
+ "SearchOctenTargetOptionsHighlight",
+ "SearchYouTarget",
+ "SearchYouTargetOptions",
+ "SearchYouTargetOptionsExtraction",
+ "SearchYouTargetOptionsExtractionFullPage",
+ "SearchTavilyTarget",
+ "SearchTavilyTargetOptions",
+ "SearchSerpAPITarget",
+ "SearchSerpAPITargetOptions",
+]
+
+
+class SearchBraveTargetOptions(TypedDict, total=False):
+ count: int
+ """Provider-native count.
+
+ Lower-only alias for max_results; cannot raise the effective result cap.
+ """
+
+ extra_snippets: bool
+ """Request additional snippets from Brave."""
+
+ goggles: str
+ """Goggles re-ranking definition URL."""
+
+ goggles_id: str
+ """Deprecated Brave Goggle identifier. Prefer goggles."""
+
+ include_fetch_metadata: bool
+ """Include Brave's fetch metadata."""
+
+ offset: int
+ """Page offset supported by Brave."""
+
+ operators: str
+ """Brave search operators."""
+
+ result_filter: str
+ """Comma-separated result types to include, e.g. "web,news"."""
+
+ search_lang: str
+ """Language of the search, e.g. "en"."""
+
+ spellcheck: bool
+ """Apply Brave's query spellcheck."""
+
+ ui_lang: str
+ """Language for UI strings in the response."""
+
+ units: Literal["metric", "imperial"]
+ """Measurement units."""
+
+
+class SearchBraveTarget(TypedDict, total=False):
+ provider: Required[Literal["brave"]]
+
+ options: SearchBraveTargetOptions
+
+
+class SearchExaTargetOptionsContents(TypedDict, total=False):
+ """Provider-native content retrieval.
+
+ Available without requesting Kernel browser retrieval; may incur provider retrieval charges.
+ """
+
+ highlights: bool
+ """Return query-relevant provider excerpts.
+
+ Defaults to true for Exa search requests; set false to disable.
+ """
+
+ text: bool
+ """Return provider page text."""
+
+
+class SearchExaTargetOptions(TypedDict, total=False):
+ category: str
+ """Provider data-category hint."""
+
+ compliance: str
+ """Provider-native compliance controls.
+
+ Requires support and authorization on the provider account.
+ """
+
+ contents: SearchExaTargetOptionsContents
+ """Provider-native content retrieval.
+
+ Available without requesting Kernel browser retrieval; may incur provider
+ retrieval charges.
+ """
+
+ max_age_hours: Annotated[float, PropertyInfo(alias="maxAgeHours")]
+ """Provider-native cache-age control.
+
+ Unlike content.max_age_hours, this retains Exa semantics, including any native
+ sentinel values. It does not imply a cross-provider freshness guarantee.
+ """
+
+ num_results: Annotated[int, PropertyInfo(alias="numResults")]
+ """Provider-native count. Lower-only alias for max_results."""
+
+ type: Literal["auto", "fast", "instant"]
+ """Search mode supported by Exa."""
+
+
+class SearchExaTarget(TypedDict, total=False):
+ provider: Required[Literal["exa"]]
+
+ options: SearchExaTargetOptions
+
+
+class SearchPerplexityTargetOptions(TypedDict, total=False):
+ last_updated_after_filter: str
+ """MM/DD/YYYY. Filters by last-updated date, not published date."""
+
+ last_updated_before_filter: str
+ """MM/DD/YYYY upper bound on last-updated date."""
+
+ max_results: int
+ """Provider-native count. Lower-only alias for max_results."""
+
+ max_tokens: int
+ """Values outside the documented range are rejected."""
+
+ max_tokens_per_page: int
+ """Per-page token cap."""
+
+ query: SequenceNotStr[str]
+ """Provider-native multi-query form, applied only to Perplexity.
+
+ Other fallback providers receive the top-level query. Each query may incur a
+ separate provider charge.
+ """
+
+ search_context_size: Literal["low", "medium", "high"]
+ """Amount of page content Perplexity extracts per result.
+
+ Defaults to medium unless max_tokens or max_tokens_per_page is set.
+ """
+
+ search_language_filter: SequenceNotStr[str]
+ """ISO 639-1 language codes, max 20."""
+
+
+class SearchPerplexityTarget(TypedDict, total=False):
+ provider: Required[Literal["perplexity"]]
+
+ options: SearchPerplexityTargetOptions
+
+
+class SearchContextTargetOptionsMarkdownOptionsPdf(TypedDict, total=False):
+ should_parse: Annotated[bool, PropertyInfo(alias="shouldParse")]
+
+
+class SearchContextTargetOptionsMarkdownOptions(TypedDict, total=False):
+ enabled: bool
+
+ include_frames: Annotated[bool, PropertyInfo(alias="includeFrames")]
+
+ include_images: Annotated[bool, PropertyInfo(alias="includeImages")]
+
+ include_links: Annotated[bool, PropertyInfo(alias="includeLinks")]
+
+ max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")]
+
+ pdf: SearchContextTargetOptionsMarkdownOptionsPdf
+
+ shorten_base64_images: Annotated[bool, PropertyInfo(alias="shortenBase64Images")]
+
+ timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")]
+
+ use_main_content_only: Annotated[bool, PropertyInfo(alias="useMainContentOnly")]
+
+ wait_for_ms: Annotated[int, PropertyInfo(alias="waitForMs")]
+
+
+class SearchContextTargetOptions(TypedDict, total=False):
+ country: str
+ """ISO 3166-1 alpha-2 country code."""
+
+ exclude_domains: Annotated[SequenceNotStr[str], PropertyInfo(alias="excludeDomains")]
+ """Blocklist of result domains."""
+
+ freshness: Literal["last_24_hours", "last_week", "last_month", "last_year"]
+ """Restrict results to content published within this window."""
+
+ include_domains: Annotated[SequenceNotStr[str], PropertyInfo(alias="includeDomains")]
+ """Allowlist of result domains."""
+
+ markdown_options: Annotated[SearchContextTargetOptionsMarkdownOptions, PropertyInfo(alias="markdownOptions")]
+
+ num_results: Annotated[int, PropertyInfo(alias="numResults")]
+ """Number of results to request from Context.dev."""
+
+ query_fanout: Annotated[bool, PropertyInfo(alias="queryFanout")]
+ """Expand the query into multiple parallel variants."""
+
+ tags: SequenceNotStr[str]
+ """Usage tracking tags."""
+
+ timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")]
+ """Context.dev request timeout in milliseconds."""
+
+
+class SearchContextTarget(TypedDict, total=False):
+ provider: Required[Literal["context"]]
+
+ options: SearchContextTargetOptions
+
+
+class SearchParallelTargetOptionsAdvancedSettingsExcerptSettings(TypedDict, total=False):
+ max_chars_per_result: Optional[int]
+
+
+class SearchParallelTargetOptionsAdvancedSettingsFetchPolicy(TypedDict, total=False):
+ disable_cache_fallback: bool
+ """When false, the provider may return cached content after live fetching fails."""
+
+ max_age_seconds: Optional[int]
+ """Native live-fetch trigger; minimum 600 seconds.
+
+ Not the unified hard-freshness control.
+ """
+
+ timeout_seconds: Optional[float]
+ """Native live-fetch timeout, bounded by the remaining overall deadline."""
+
+
+class SearchParallelTargetOptionsAdvancedSettingsSourcePolicy(TypedDict, total=False):
+ after_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+ """Native publication-date lower bound."""
+
+ exclude_domains: SequenceNotStr[str]
+ """
+ Native exclusions; the provider ignores these when native include_domains is
+ non-empty.
+ """
+
+ include_domains: SequenceNotStr[str]
+ """Native domain/path restrictions.
+
+ Explicit unified domain parameters take precedence. Combined include/exclude
+ native lists cannot exceed 200 entries.
+ """
+
+
+class SearchParallelTargetOptionsAdvancedSettings(TypedDict, total=False):
+ """Explicit search settings.
+
+ Unified search parameters are re-applied to overlapping settings; native result counts are lower-only.
+ """
+
+ excerpt_settings: SearchParallelTargetOptionsAdvancedSettingsExcerptSettings
+
+ fetch_policy: SearchParallelTargetOptionsAdvancedSettingsFetchPolicy
+
+ location: Optional[str]
+ """Native ISO 3166-1 alpha-2 location preference."""
+
+ max_results: Optional[int]
+ """Native count; lower-only alias for unified max_results."""
+
+ source_policy: SearchParallelTargetOptionsAdvancedSettingsSourcePolicy
+
+
+class SearchParallelTargetOptions(TypedDict, total=False):
+ advanced_settings: SearchParallelTargetOptionsAdvancedSettings
+ """Explicit search settings.
+
+ Unified search parameters are re-applied to overlapping settings; native result
+ counts are lower-only.
+ """
+
+ client_model: str
+ """Client model hint."""
+
+ max_chars_total: int
+ """Cap total characters returned."""
+
+ mode: Literal["turbo", "fast", "basic", "advanced"]
+ """Search mode.
+
+ Basic is used when omitted; each mode can have different latency and charges.
+ """
+
+ objective: str
+ """The goal behind the search, stated separately from the query."""
+
+ search_queries: SequenceNotStr[str]
+ """Provider-native multi-query search. Defaults to [query] for this provider."""
+
+ session_id: str
+ """Group related searches."""
+
+
+class SearchParallelTarget(TypedDict, total=False):
+ provider: Required[Literal["parallel"]]
+
+ options: SearchParallelTargetOptions
+
+
+class SearchValyuTargetOptions(TypedDict, total=False):
+ fast_mode: bool
+ """Trade depth for latency."""
+
+ historical_cache: bool
+ """Allow historical cached results."""
+
+ include_abstracts: bool
+ """Include abstracts for academic sources."""
+
+ instructions: str
+ """Natural-language retrieval guidance."""
+
+ is_tool_call: bool
+ """Mark the search as an agent tool call."""
+
+ max_num_results: int
+ """Provider-native count. Lower-only alias for max_results."""
+
+ max_price: float
+ """Provider-native USD-per-thousand-results price ceiling. Forwarded to Valyu."""
+
+ relevance_threshold: float
+ """Provider-native minimum relevance threshold.
+
+ Not a normalized cross-provider score.
+ """
+
+ response_length: Literal["short", "medium", "large", "max"]
+ """Provider result-content length preset."""
+
+ search_type: str
+ """Corpus selector, including all, web, proprietary, and news.
+
+ Provider corpus choice may change billing; Kernel does not force web-only
+ searches.
+ """
+
+ source_biases: SequenceNotStr[str]
+ """Bias retrieval toward these sources."""
+
+ url_only: bool
+ """Return URLs without content."""
+
+
+class SearchValyuTarget(TypedDict, total=False):
+ provider: Required[Literal["valyu"]]
+
+ options: SearchValyuTargetOptions
+
+
+class SearchOctenTargetOptionsFullContent(TypedDict, total=False):
+ enable: bool
+
+ max_tokens: int
+
+
+class SearchOctenTargetOptionsHighlight(TypedDict, total=False):
+ enable: bool
+
+ max_tokens: int
+
+
+class SearchOctenTargetOptions(TypedDict, total=False):
+ count: int
+
+ end_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+
+ exclude_domains: SequenceNotStr[str]
+
+ exclude_text: SequenceNotStr[str]
+
+ format: Literal["markdown", "text"]
+
+ full_content: SearchOctenTargetOptionsFullContent
+
+ highlight: SearchOctenTargetOptionsHighlight
+
+ include_domains: SequenceNotStr[str]
+
+ include_images: bool
+
+ include_text: SequenceNotStr[str]
+
+ language: SequenceNotStr[str]
+
+ safesearch: Literal["off", "strict"]
+
+ start_time: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+
+ time_basis: Literal["auto", "published", "crawled"]
+
+ time_range: Literal["day", "week", "month", "year", "d", "w", "m", "y"]
+
+ topic: Literal["general", "news"]
+
+
+class SearchOctenTarget(TypedDict, total=False):
+ provider: Required[Literal["octen"]]
+
+ options: SearchOctenTargetOptions
+
+
+class SearchYouTargetOptionsExtractionFullPage(TypedDict, total=False):
+ extraction_formats: List[Literal["html", "markdown"]]
+
+
+class SearchYouTargetOptionsExtraction(TypedDict, total=False):
+ """Provider-native page extraction.
+
+ Both modes may incur per-row charges; full_page may retrieve web and news rows.
+ """
+
+ extraction_mode: Required[Literal["highlights", "full_page"]]
+
+ full_page: SearchYouTargetOptionsExtractionFullPage
+
+
+class SearchYouTargetOptions(TypedDict, total=False):
+ boost_domains: SequenceNotStr[str]
+ """Prefer these domains without excluding others.
+
+ Cannot be combined with include_domains if the provider does not accept the
+ combination.
+ """
+
+ count: int
+ """Provider-native per-section count.
+
+ Lower-only alias for max_results. Web and news sections may produce more rows
+ than Kernel returns.
+ """
+
+ crawl_timeout: int
+ """
+ Native extraction timeout in seconds, bounded by the remaining Kernel request
+ deadline.
+ """
+
+ extraction: SearchYouTargetOptionsExtraction
+ """Provider-native page extraction.
+
+ Both modes may incur per-row charges; full_page may retrieve web and news rows.
+ """
+
+ knowledge: Literal["core"]
+ """Request licensed-data output.
+
+ URL-less knowledge entries are not converted into web results; include_raw
+ exposes the full provider response separately.
+ """
+
+ language: str
+ """BCP 47 result language from You.com's 51-value enum, e.g.
+
+ "EN", "JA". Default EN.
+ """
+
+ offset: int
+ """Page offset supported by You.com."""
+
+
+class SearchYouTarget(TypedDict, total=False):
+ provider: Required[Literal["you"]]
+
+ options: SearchYouTargetOptions
+
+
+class SearchTavilyTargetOptions(TypedDict, total=False):
+ auto_parameters: bool
+ """Allow the provider to choose search parameters.
+
+ May select a different billing tier; explicit caller values retain
+ provider-native precedence.
+ """
+
+ chunks_per_source: int
+ """Provider excerpts per source, up to 500 characters each."""
+
+ exact_match: bool
+ """Require the quoted phrases in the query verbatim, bypassing semantic matches."""
+
+ filter_by_language: bool
+ """Strictly filter non-matching languages. Requires `language`."""
+
+ include_answer: Union[bool, Literal["basic", "advanced"]]
+ """Request the provider's generated answer.
+
+ Returned as answer on the search response, independently of include_raw.
+ """
+
+ include_domains_mode: Literal["filter", "boost"]
+ """Native filter versus ranking boost semantics.
+
+ Boost influences ranking rather than restricting results to the listed domains.
+ Requires include_domains.
+ """
+
+ include_favicon: bool
+ """Favicon URL per result."""
+
+ include_image_descriptions: bool
+ """Describe each image. Needs include_images."""
+
+ include_images: bool
+ """Query-related images plus per-result images."""
+
+ include_raw_content: Union[bool, Literal["markdown", "text"]]
+ """Request native full-page content.
+
+ Defaults to markdown when omitted for this provider, False disables that native
+ retrieval; it does not disable explicitly requested Kernel browser retrieval.
+ """
+
+ language: str
+ """ISO 639-1 code or English language name.
+
+ Ranking boost unless filter_by_language.
+ """
+
+ max_results: int
+ """Provider-native count. Lower-only alias for max_results."""
+
+ search_depth: Literal["advanced", "basic", "fast", "ultra-fast"]
+ """Provider relevance and latency tier.
+
+ Some tiers cannot be combined with native safe search; conflicts are described
+ in warnings.
+ """
+
+ topic: Literal["general", "news", "finance"]
+ """Provider corpus selector. Publication metadata depends on the selected topic."""
+
+
+class SearchTavilyTarget(TypedDict, total=False):
+ provider: Required[Literal["tavily"]]
+
+ options: SearchTavilyTargetOptions
+
+
+class SearchSerpAPITargetOptions(TypedDict, total=False):
+ engine: Required[str]
+ """SerpApi engine identifier.
+
+ The Kernel integration currently supports google only.
+ """
+
+ device: Literal["desktop", "mobile", "tablet"]
+ """Device profile used for the search."""
+
+ filter: Literal[0, 1]
+ """Google duplicate-content filter."""
+
+ gl: str
+ """Two-letter Google country code."""
+
+ google_domain: str
+ """Google domain to search when using the google engine."""
+
+ hl: str
+ """Interface language code."""
+
+ location: str
+ """Free-form geographic location used for localized results."""
+
+ nfpr: Literal[0, 1]
+ """Google auto-correction filter."""
+
+ no_cache: bool
+ """When true, bypass SerpApi cached results when supported."""
+
+ num: int
+ """Number of results requested from the search engine."""
+
+ safe: Literal["active", "off"]
+ """Safe-search setting for engines that support it."""
+
+ start: int
+ """Zero-based result offset for pagination."""
+
+ tbm: str
+ """Google vertical search selector, such as images, video, news, or shopping."""
+
+ tbs: str
+ """Google time and search modifiers, including freshness filters."""
+
+
+class SearchSerpAPITarget(TypedDict, total=False):
+ provider: Required[Literal["serpapi"]]
+
+ options: SearchSerpAPITargetOptions
+
+
+ProviderTargetParam: TypeAlias = Union[
+ SearchBraveTarget,
+ SearchExaTarget,
+ SearchPerplexityTarget,
+ SearchContextTarget,
+ SearchParallelTarget,
+ SearchValyuTarget,
+ SearchOctenTarget,
+ SearchYouTarget,
+ SearchTavilyTarget,
+ SearchSerpAPITarget,
+]
diff --git a/src/kernel/types/proxy.py b/src/kernel/types/proxy.py
new file mode 100644
index 00000000..af5045f9
--- /dev/null
+++ b/src/kernel/types/proxy.py
@@ -0,0 +1,148 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from .._utils import PropertyInfo
+from .._models import BaseModel
+
+__all__ = [
+ "Proxy",
+ "ConfigRegistryDirectProxy",
+ "ConfigRegistryManagedProxy",
+ "ConfigRegistryManagedProxyCreate",
+ "ConfigRegistryManagedProxyCreateConfig",
+ "ConfigRegistryManagedProxyCreateConfigIspProxyConfig",
+ "ConfigRegistryManagedProxyCreateConfigResidentialProxyConfig",
+ "ConfigRegistryManagedProxyCreateConfigMobileProxyConfig",
+ "ConfigRegistryManagedProxyCreateConfigCreateCustomProxyConfig",
+]
+
+
+class ConfigRegistryDirectProxy(BaseModel):
+ """Direct egress recipe. Pass `{ "mode": "direct" }` as the browser's `proxy`."""
+
+ mode: Literal["direct"]
+
+
+class ConfigRegistryManagedProxyCreateConfigIspProxyConfig(BaseModel):
+ """Configuration for an ISP proxy."""
+
+ country: Optional[str] = None
+ """ISO 3166 country code.
+
+ Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
+ provided.
+ """
+
+
+class ConfigRegistryManagedProxyCreateConfigResidentialProxyConfig(BaseModel):
+ """Configuration for residential proxies."""
+
+ asn: Optional[str] = None
+ """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html"""
+
+ city: Optional[str] = None
+ """City name (no spaces, e.g.
+
+ `sanfrancisco`). If provided, `country` must also be provided.
+ """
+
+ country: Optional[str] = None
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
+
+ os: Optional[Literal["windows", "macos", "android"]] = None
+ """Operating system of the residential device."""
+
+ state: Optional[str] = None
+ """Two-letter state code."""
+
+ zip: Optional[str] = None
+ """US ZIP code."""
+
+
+class ConfigRegistryManagedProxyCreateConfigMobileProxyConfig(BaseModel):
+ """Configuration for mobile proxies."""
+
+ city: Optional[str] = None
+ """Provider city alias. Mobile carrier routing can make observed geo vary."""
+
+ country: Optional[str] = None
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
+
+ state: Optional[str] = None
+ """US-only state code. Mobile carrier routing can make observed geo vary."""
+
+
+class ConfigRegistryManagedProxyCreateConfigCreateCustomProxyConfig(BaseModel):
+ """Configuration for a custom proxy (e.g., private proxy server)."""
+
+ host: str
+ """Proxy host address or IP."""
+
+ port: int
+ """Proxy port."""
+
+ ca_bundle: Optional[str] = None
+ """PEM-encoded CA certificate bundle the proxy re-signs upstream TLS with.
+
+ Provide when the proxy terminates TLS (MITM) so the browser trusts its
+ certificates. May contain multiple concatenated certificates.
+ """
+
+ password: Optional[str] = None
+ """Password for proxy authentication."""
+
+ username: Optional[str] = None
+ """Username for proxy authentication."""
+
+
+ConfigRegistryManagedProxyCreateConfig: TypeAlias = Union[
+ ConfigRegistryManagedProxyCreateConfigIspProxyConfig,
+ ConfigRegistryManagedProxyCreateConfigResidentialProxyConfig,
+ ConfigRegistryManagedProxyCreateConfigMobileProxyConfig,
+ ConfigRegistryManagedProxyCreateConfigCreateCustomProxyConfig,
+]
+
+
+class ConfigRegistryManagedProxyCreate(BaseModel):
+ """Configuration for routing traffic through a proxy."""
+
+ type: Literal["isp", "residential", "mobile", "custom"]
+
+ bypass_hosts: Optional[List[str]] = None
+ """Hostnames that should bypass the parent proxy and connect directly."""
+
+ config: Optional[ConfigRegistryManagedProxyCreateConfig] = None
+ """Configuration for an ISP proxy."""
+
+ name: Optional[str] = None
+ """Readable name of the proxy."""
+
+ protocol: Optional[Literal["http", "https"]] = None
+ """Protocol to use for the proxy connection."""
+
+
+class ConfigRegistryManagedProxy(BaseModel):
+ """Managed proxy recipe.
+
+ `create` is a non-idempotent `POST /proxies` payload:
+ create the resource once, retain its ID, and reuse that ID as the browser's
+ `proxy.id`. Do not submit this recipe before every browser session.
+ """
+
+ create: ConfigRegistryManagedProxyCreate
+ """Configuration for routing traffic through a proxy."""
+
+ mode: Literal["managed"]
+
+
+Proxy: TypeAlias = Annotated[
+ Union[ConfigRegistryDirectProxy, ConfigRegistryManagedProxy], PropertyInfo(discriminator="mode")
+]
diff --git a/src/kernel/types/proxy_check_params.py b/src/kernel/types/proxy_check_params.py
index de3a8c3d..056a91d0 100644
--- a/src/kernel/types/proxy_check_params.py
+++ b/src/kernel/types/proxy_check_params.py
@@ -13,11 +13,13 @@ class ProxyCheckParams(TypedDict, total=False):
If provided, the proxy check will test connectivity to this URL instead of the
default test URLs. Only HTTP and HTTPS schemes are allowed, and the URL must
- resolve to a public IP address. For ISP and datacenter proxies, the exit IP is
- stable, so a successful check reliably indicates that subsequent browser
- sessions will reach the target site with the same IP. For residential and mobile
- proxies, the exit node changes between requests, so a successful check validates
- proxy configuration but does not guarantee that a subsequent browser session
- will use the same exit IP or reach the same site — it is useful for verifying
- credentials and connectivity, not for predicting site-specific behavior.
+ resolve to a public IP address. For ISP proxies, the exit IP is stable, so a
+ successful check reliably indicates that subsequent browser sessions will reach
+ the target site with the same IP. For residential and mobile proxies, the exit
+ node changes between requests, so a successful check validates proxy
+ configuration but does not guarantee that a subsequent browser session will use
+ the same exit IP or reach the same site — it is useful for verifying credentials
+ and connectivity, not for predicting site-specific behavior. When provided, the
+ check result does not update the proxy's health status, since a failure may
+ indicate a problem with the target site rather than the proxy itself.
"""
diff --git a/src/kernel/types/proxy_check_response.py b/src/kernel/types/proxy_check_response.py
index c26d665d..75ee4655 100644
--- a/src/kernel/types/proxy_check_response.py
+++ b/src/kernel/types/proxy_check_response.py
@@ -9,7 +9,6 @@
__all__ = [
"ProxyCheckResponse",
"Config",
- "ConfigDatacenterProxyConfig",
"ConfigIspProxyConfig",
"ConfigResidentialProxyConfig",
"ConfigMobileProxyConfig",
@@ -17,18 +16,15 @@
]
-class ConfigDatacenterProxyConfig(BaseModel):
- """Configuration for a datacenter proxy."""
-
- country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
-
-
class ConfigIspProxyConfig(BaseModel):
"""Configuration for an ISP proxy."""
country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
+ """ISO 3166 country code.
+
+ Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
+ provided.
+ """
class ConfigResidentialProxyConfig(BaseModel):
@@ -44,7 +40,10 @@ class ConfigResidentialProxyConfig(BaseModel):
"""
country: Optional[str] = None
- """ISO 3166 country code."""
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
os: Optional[Literal["windows", "macos", "android"]] = None
"""Operating system of the residential device."""
@@ -59,87 +58,17 @@ class ConfigResidentialProxyConfig(BaseModel):
class ConfigMobileProxyConfig(BaseModel):
"""Configuration for mobile proxies."""
- asn: Optional[str] = None
- """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html"""
-
- carrier: Optional[
- Literal[
- "a1",
- "aircel",
- "airtel",
- "att",
- "celcom",
- "chinamobile",
- "claro",
- "comcast",
- "cox",
- "digi",
- "dt",
- "docomo",
- "dtac",
- "etisalat",
- "idea",
- "kyivstar",
- "meo",
- "megafon",
- "mtn",
- "mtnza",
- "mts",
- "optus",
- "orange",
- "qwest",
- "reliance_jio",
- "robi",
- "sprint",
- "telefonica",
- "telstra",
- "tmobile",
- "tigo",
- "tim",
- "verizon",
- "vimpelcom",
- "vodacomza",
- "vodafone",
- "vivo",
- "zain",
- "vivabo",
- "telenormyanmar",
- "kcelljsc",
- "swisscom",
- "singtel",
- "asiacell",
- "windit",
- "cellc",
- "ooredoo",
- "drei",
- "umobile",
- "cableone",
- "proximus",
- "tele2",
- "mobitel",
- "o2",
- "bouygues",
- "free",
- "sfr",
- "digicel",
- ]
- ] = None
- """Mobile carrier."""
-
city: Optional[str] = None
- """City name (no spaces, e.g.
-
- `sanfrancisco`). If provided, `country` must also be provided.
- """
+ """Provider city alias. Mobile carrier routing can make observed geo vary."""
country: Optional[str] = None
- """ISO 3166 country code"""
+ """ISO 3166 country code.
- state: Optional[str] = None
- """Two-letter state code."""
+ If omitted, the proxy uses the global pool without country targeting.
+ """
- zip: Optional[str] = None
- """US ZIP code."""
+ state: Optional[str] = None
+ """US-only state code. Mobile carrier routing can make observed geo vary."""
class ConfigCustomProxyConfig(BaseModel):
@@ -151,6 +80,9 @@ class ConfigCustomProxyConfig(BaseModel):
port: int
"""Proxy port."""
+ has_ca_bundle: Optional[bool] = None
+ """Whether the proxy has a custom CA bundle configured."""
+
has_password: Optional[bool] = None
"""Whether the proxy has a password."""
@@ -159,23 +91,14 @@ class ConfigCustomProxyConfig(BaseModel):
Config: TypeAlias = Union[
- ConfigDatacenterProxyConfig,
- ConfigIspProxyConfig,
- ConfigResidentialProxyConfig,
- ConfigMobileProxyConfig,
- ConfigCustomProxyConfig,
+ ConfigIspProxyConfig, ConfigResidentialProxyConfig, ConfigMobileProxyConfig, ConfigCustomProxyConfig
]
class ProxyCheckResponse(BaseModel):
"""Configuration for routing traffic through a proxy."""
- type: Literal["datacenter", "isp", "residential", "mobile", "custom"]
- """Proxy type to use.
-
- In terms of quality for avoiding bot-detection, from best to worst: `mobile` >
- `residential` > `isp` > `datacenter`.
- """
+ type: Literal["isp", "residential", "mobile", "custom"]
id: Optional[str] = None
@@ -183,7 +106,7 @@ class ProxyCheckResponse(BaseModel):
"""Hostnames that should bypass the parent proxy and connect directly."""
config: Optional[Config] = None
- """Configuration specific to the selected proxy `type`."""
+ """Configuration for an ISP proxy."""
ip_address: Optional[str] = None
"""IP address that the proxy uses when making requests."""
diff --git a/src/kernel/types/proxy_create_params.py b/src/kernel/types/proxy_create_params.py
index 175b95ff..6872e9fb 100644
--- a/src/kernel/types/proxy_create_params.py
+++ b/src/kernel/types/proxy_create_params.py
@@ -10,7 +10,6 @@
__all__ = [
"ProxyCreateParams",
"Config",
- "ConfigDatacenterProxyConfig",
"ConfigIspProxyConfig",
"ConfigResidentialProxyConfig",
"ConfigMobileProxyConfig",
@@ -19,18 +18,13 @@
class ProxyCreateParams(TypedDict, total=False):
- type: Required[Literal["datacenter", "isp", "residential", "mobile", "custom"]]
- """Proxy type to use.
-
- In terms of quality for avoiding bot-detection, from best to worst: `mobile` >
- `residential` > `isp` > `datacenter`.
- """
+ type: Required[Literal["isp", "residential", "mobile", "custom"]]
bypass_hosts: SequenceNotStr[str]
"""Hostnames that should bypass the parent proxy and connect directly."""
config: Config
- """Configuration specific to the selected proxy `type`."""
+ """Configuration for an ISP proxy."""
name: str
"""Readable name of the proxy."""
@@ -39,18 +33,15 @@ class ProxyCreateParams(TypedDict, total=False):
"""Protocol to use for the proxy connection."""
-class ConfigDatacenterProxyConfig(TypedDict, total=False):
- """Configuration for a datacenter proxy."""
-
- country: str
- """ISO 3166 country code. Defaults to US if not provided."""
-
-
class ConfigIspProxyConfig(TypedDict, total=False):
"""Configuration for an ISP proxy."""
country: str
- """ISO 3166 country code. Defaults to US if not provided."""
+ """ISO 3166 country code.
+
+ Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
+ provided.
+ """
class ConfigResidentialProxyConfig(TypedDict, total=False):
@@ -66,7 +57,10 @@ class ConfigResidentialProxyConfig(TypedDict, total=False):
"""
country: str
- """ISO 3166 country code."""
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
os: Literal["windows", "macos", "android"]
"""Operating system of the residential device."""
@@ -81,85 +75,17 @@ class ConfigResidentialProxyConfig(TypedDict, total=False):
class ConfigMobileProxyConfig(TypedDict, total=False):
"""Configuration for mobile proxies."""
- asn: str
- """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html"""
-
- carrier: Literal[
- "a1",
- "aircel",
- "airtel",
- "att",
- "celcom",
- "chinamobile",
- "claro",
- "comcast",
- "cox",
- "digi",
- "dt",
- "docomo",
- "dtac",
- "etisalat",
- "idea",
- "kyivstar",
- "meo",
- "megafon",
- "mtn",
- "mtnza",
- "mts",
- "optus",
- "orange",
- "qwest",
- "reliance_jio",
- "robi",
- "sprint",
- "telefonica",
- "telstra",
- "tmobile",
- "tigo",
- "tim",
- "verizon",
- "vimpelcom",
- "vodacomza",
- "vodafone",
- "vivo",
- "zain",
- "vivabo",
- "telenormyanmar",
- "kcelljsc",
- "swisscom",
- "singtel",
- "asiacell",
- "windit",
- "cellc",
- "ooredoo",
- "drei",
- "umobile",
- "cableone",
- "proximus",
- "tele2",
- "mobitel",
- "o2",
- "bouygues",
- "free",
- "sfr",
- "digicel",
- ]
- """Mobile carrier."""
-
city: str
- """City name (no spaces, e.g.
-
- `sanfrancisco`). If provided, `country` must also be provided.
- """
+ """Provider city alias. Mobile carrier routing can make observed geo vary."""
country: str
- """ISO 3166 country code"""
+ """ISO 3166 country code.
- state: str
- """Two-letter state code."""
+ If omitted, the proxy uses the global pool without country targeting.
+ """
- zip: str
- """US ZIP code."""
+ state: str
+ """US-only state code. Mobile carrier routing can make observed geo vary."""
class ConfigCreateCustomProxyConfig(TypedDict, total=False):
@@ -171,6 +97,13 @@ class ConfigCreateCustomProxyConfig(TypedDict, total=False):
port: Required[int]
"""Proxy port."""
+ ca_bundle: str
+ """PEM-encoded CA certificate bundle the proxy re-signs upstream TLS with.
+
+ Provide when the proxy terminates TLS (MITM) so the browser trusts its
+ certificates. May contain multiple concatenated certificates.
+ """
+
password: str
"""Password for proxy authentication."""
@@ -179,9 +112,5 @@ class ConfigCreateCustomProxyConfig(TypedDict, total=False):
Config: TypeAlias = Union[
- ConfigDatacenterProxyConfig,
- ConfigIspProxyConfig,
- ConfigResidentialProxyConfig,
- ConfigMobileProxyConfig,
- ConfigCreateCustomProxyConfig,
+ ConfigIspProxyConfig, ConfigResidentialProxyConfig, ConfigMobileProxyConfig, ConfigCreateCustomProxyConfig
]
diff --git a/src/kernel/types/proxy_create_response.py b/src/kernel/types/proxy_create_response.py
index d317662f..f4d96af8 100644
--- a/src/kernel/types/proxy_create_response.py
+++ b/src/kernel/types/proxy_create_response.py
@@ -9,7 +9,6 @@
__all__ = [
"ProxyCreateResponse",
"Config",
- "ConfigDatacenterProxyConfig",
"ConfigIspProxyConfig",
"ConfigResidentialProxyConfig",
"ConfigMobileProxyConfig",
@@ -17,18 +16,15 @@
]
-class ConfigDatacenterProxyConfig(BaseModel):
- """Configuration for a datacenter proxy."""
-
- country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
-
-
class ConfigIspProxyConfig(BaseModel):
"""Configuration for an ISP proxy."""
country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
+ """ISO 3166 country code.
+
+ Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
+ provided.
+ """
class ConfigResidentialProxyConfig(BaseModel):
@@ -44,7 +40,10 @@ class ConfigResidentialProxyConfig(BaseModel):
"""
country: Optional[str] = None
- """ISO 3166 country code."""
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
os: Optional[Literal["windows", "macos", "android"]] = None
"""Operating system of the residential device."""
@@ -59,87 +58,17 @@ class ConfigResidentialProxyConfig(BaseModel):
class ConfigMobileProxyConfig(BaseModel):
"""Configuration for mobile proxies."""
- asn: Optional[str] = None
- """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html"""
-
- carrier: Optional[
- Literal[
- "a1",
- "aircel",
- "airtel",
- "att",
- "celcom",
- "chinamobile",
- "claro",
- "comcast",
- "cox",
- "digi",
- "dt",
- "docomo",
- "dtac",
- "etisalat",
- "idea",
- "kyivstar",
- "meo",
- "megafon",
- "mtn",
- "mtnza",
- "mts",
- "optus",
- "orange",
- "qwest",
- "reliance_jio",
- "robi",
- "sprint",
- "telefonica",
- "telstra",
- "tmobile",
- "tigo",
- "tim",
- "verizon",
- "vimpelcom",
- "vodacomza",
- "vodafone",
- "vivo",
- "zain",
- "vivabo",
- "telenormyanmar",
- "kcelljsc",
- "swisscom",
- "singtel",
- "asiacell",
- "windit",
- "cellc",
- "ooredoo",
- "drei",
- "umobile",
- "cableone",
- "proximus",
- "tele2",
- "mobitel",
- "o2",
- "bouygues",
- "free",
- "sfr",
- "digicel",
- ]
- ] = None
- """Mobile carrier."""
-
city: Optional[str] = None
- """City name (no spaces, e.g.
-
- `sanfrancisco`). If provided, `country` must also be provided.
- """
+ """Provider city alias. Mobile carrier routing can make observed geo vary."""
country: Optional[str] = None
- """ISO 3166 country code"""
+ """ISO 3166 country code.
- state: Optional[str] = None
- """Two-letter state code."""
+ If omitted, the proxy uses the global pool without country targeting.
+ """
- zip: Optional[str] = None
- """US ZIP code."""
+ state: Optional[str] = None
+ """US-only state code. Mobile carrier routing can make observed geo vary."""
class ConfigCustomProxyConfig(BaseModel):
@@ -151,6 +80,9 @@ class ConfigCustomProxyConfig(BaseModel):
port: int
"""Proxy port."""
+ has_ca_bundle: Optional[bool] = None
+ """Whether the proxy has a custom CA bundle configured."""
+
has_password: Optional[bool] = None
"""Whether the proxy has a password."""
@@ -159,23 +91,14 @@ class ConfigCustomProxyConfig(BaseModel):
Config: TypeAlias = Union[
- ConfigDatacenterProxyConfig,
- ConfigIspProxyConfig,
- ConfigResidentialProxyConfig,
- ConfigMobileProxyConfig,
- ConfigCustomProxyConfig,
+ ConfigIspProxyConfig, ConfigResidentialProxyConfig, ConfigMobileProxyConfig, ConfigCustomProxyConfig
]
class ProxyCreateResponse(BaseModel):
"""Configuration for routing traffic through a proxy."""
- type: Literal["datacenter", "isp", "residential", "mobile", "custom"]
- """Proxy type to use.
-
- In terms of quality for avoiding bot-detection, from best to worst: `mobile` >
- `residential` > `isp` > `datacenter`.
- """
+ type: Literal["isp", "residential", "mobile", "custom"]
id: Optional[str] = None
@@ -183,7 +106,7 @@ class ProxyCreateResponse(BaseModel):
"""Hostnames that should bypass the parent proxy and connect directly."""
config: Optional[Config] = None
- """Configuration specific to the selected proxy `type`."""
+ """Configuration for an ISP proxy."""
ip_address: Optional[str] = None
"""IP address that the proxy uses when making requests."""
diff --git a/src/kernel/types/proxy_list_params.py b/src/kernel/types/proxy_list_params.py
new file mode 100644
index 00000000..1c7b30e6
--- /dev/null
+++ b/src/kernel/types/proxy_list_params.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ProxyListParams"]
+
+
+class ProxyListParams(TypedDict, total=False):
+ limit: int
+ """Limit the number of proxies to return."""
+
+ name: str
+ """Exact-match filter on proxy name using the database collation.
+
+ In production, matching is case- and accent-insensitive. Names are not required
+ to be unique, so multiple proxies may match.
+ """
+
+ offset: int
+ """Offset the number of proxies to return."""
+
+ query: str
+ """Case-insensitive substring match against proxy name, host, or IP address.
+
+ IDs match by exact value.
+ """
diff --git a/src/kernel/types/proxy_list_response.py b/src/kernel/types/proxy_list_response.py
index bbbe17c7..82d1bd18 100644
--- a/src/kernel/types/proxy_list_response.py
+++ b/src/kernel/types/proxy_list_response.py
@@ -8,31 +8,26 @@
__all__ = [
"ProxyListResponse",
- "ProxyListResponseItem",
- "ProxyListResponseItemConfig",
- "ProxyListResponseItemConfigDatacenterProxyConfig",
- "ProxyListResponseItemConfigIspProxyConfig",
- "ProxyListResponseItemConfigResidentialProxyConfig",
- "ProxyListResponseItemConfigMobileProxyConfig",
- "ProxyListResponseItemConfigCustomProxyConfig",
+ "Config",
+ "ConfigIspProxyConfig",
+ "ConfigResidentialProxyConfig",
+ "ConfigMobileProxyConfig",
+ "ConfigCustomProxyConfig",
]
-class ProxyListResponseItemConfigDatacenterProxyConfig(BaseModel):
- """Configuration for a datacenter proxy."""
-
- country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
-
-
-class ProxyListResponseItemConfigIspProxyConfig(BaseModel):
+class ConfigIspProxyConfig(BaseModel):
"""Configuration for an ISP proxy."""
country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
+ """ISO 3166 country code.
+
+ Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
+ provided.
+ """
-class ProxyListResponseItemConfigResidentialProxyConfig(BaseModel):
+class ConfigResidentialProxyConfig(BaseModel):
"""Configuration for residential proxies."""
asn: Optional[str] = None
@@ -45,7 +40,10 @@ class ProxyListResponseItemConfigResidentialProxyConfig(BaseModel):
"""
country: Optional[str] = None
- """ISO 3166 country code."""
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
os: Optional[Literal["windows", "macos", "android"]] = None
"""Operating system of the residential device."""
@@ -57,93 +55,23 @@ class ProxyListResponseItemConfigResidentialProxyConfig(BaseModel):
"""US ZIP code."""
-class ProxyListResponseItemConfigMobileProxyConfig(BaseModel):
+class ConfigMobileProxyConfig(BaseModel):
"""Configuration for mobile proxies."""
- asn: Optional[str] = None
- """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html"""
-
- carrier: Optional[
- Literal[
- "a1",
- "aircel",
- "airtel",
- "att",
- "celcom",
- "chinamobile",
- "claro",
- "comcast",
- "cox",
- "digi",
- "dt",
- "docomo",
- "dtac",
- "etisalat",
- "idea",
- "kyivstar",
- "meo",
- "megafon",
- "mtn",
- "mtnza",
- "mts",
- "optus",
- "orange",
- "qwest",
- "reliance_jio",
- "robi",
- "sprint",
- "telefonica",
- "telstra",
- "tmobile",
- "tigo",
- "tim",
- "verizon",
- "vimpelcom",
- "vodacomza",
- "vodafone",
- "vivo",
- "zain",
- "vivabo",
- "telenormyanmar",
- "kcelljsc",
- "swisscom",
- "singtel",
- "asiacell",
- "windit",
- "cellc",
- "ooredoo",
- "drei",
- "umobile",
- "cableone",
- "proximus",
- "tele2",
- "mobitel",
- "o2",
- "bouygues",
- "free",
- "sfr",
- "digicel",
- ]
- ] = None
- """Mobile carrier."""
-
city: Optional[str] = None
- """City name (no spaces, e.g.
-
- `sanfrancisco`). If provided, `country` must also be provided.
- """
+ """Provider city alias. Mobile carrier routing can make observed geo vary."""
country: Optional[str] = None
- """ISO 3166 country code"""
+ """ISO 3166 country code.
- state: Optional[str] = None
- """Two-letter state code."""
+ If omitted, the proxy uses the global pool without country targeting.
+ """
- zip: Optional[str] = None
- """US ZIP code."""
+ state: Optional[str] = None
+ """US-only state code. Mobile carrier routing can make observed geo vary."""
-class ProxyListResponseItemConfigCustomProxyConfig(BaseModel):
+class ConfigCustomProxyConfig(BaseModel):
"""Configuration for a custom proxy (e.g., private proxy server)."""
host: str
@@ -152,6 +80,9 @@ class ProxyListResponseItemConfigCustomProxyConfig(BaseModel):
port: int
"""Proxy port."""
+ has_ca_bundle: Optional[bool] = None
+ """Whether the proxy has a custom CA bundle configured."""
+
has_password: Optional[bool] = None
"""Whether the proxy has a password."""
@@ -159,32 +90,23 @@ class ProxyListResponseItemConfigCustomProxyConfig(BaseModel):
"""Username for proxy authentication."""
-ProxyListResponseItemConfig: TypeAlias = Union[
- ProxyListResponseItemConfigDatacenterProxyConfig,
- ProxyListResponseItemConfigIspProxyConfig,
- ProxyListResponseItemConfigResidentialProxyConfig,
- ProxyListResponseItemConfigMobileProxyConfig,
- ProxyListResponseItemConfigCustomProxyConfig,
+Config: TypeAlias = Union[
+ ConfigIspProxyConfig, ConfigResidentialProxyConfig, ConfigMobileProxyConfig, ConfigCustomProxyConfig
]
-class ProxyListResponseItem(BaseModel):
+class ProxyListResponse(BaseModel):
"""Configuration for routing traffic through a proxy."""
- type: Literal["datacenter", "isp", "residential", "mobile", "custom"]
- """Proxy type to use.
-
- In terms of quality for avoiding bot-detection, from best to worst: `mobile` >
- `residential` > `isp` > `datacenter`.
- """
+ type: Literal["isp", "residential", "mobile", "custom"]
id: Optional[str] = None
bypass_hosts: Optional[List[str]] = None
"""Hostnames that should bypass the parent proxy and connect directly."""
- config: Optional[ProxyListResponseItemConfig] = None
- """Configuration specific to the selected proxy `type`."""
+ config: Optional[Config] = None
+ """Configuration for an ISP proxy."""
ip_address: Optional[str] = None
"""IP address that the proxy uses when making requests."""
@@ -200,6 +122,3 @@ class ProxyListResponseItem(BaseModel):
status: Optional[Literal["available", "unavailable"]] = None
"""Current health status of the proxy."""
-
-
-ProxyListResponse: TypeAlias = List[ProxyListResponseItem]
diff --git a/src/kernel/types/proxy_retrieve_response.py b/src/kernel/types/proxy_retrieve_response.py
index 6b0b1bbe..c59de78a 100644
--- a/src/kernel/types/proxy_retrieve_response.py
+++ b/src/kernel/types/proxy_retrieve_response.py
@@ -9,7 +9,6 @@
__all__ = [
"ProxyRetrieveResponse",
"Config",
- "ConfigDatacenterProxyConfig",
"ConfigIspProxyConfig",
"ConfigResidentialProxyConfig",
"ConfigMobileProxyConfig",
@@ -17,18 +16,15 @@
]
-class ConfigDatacenterProxyConfig(BaseModel):
- """Configuration for a datacenter proxy."""
-
- country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
-
-
class ConfigIspProxyConfig(BaseModel):
"""Configuration for an ISP proxy."""
country: Optional[str] = None
- """ISO 3166 country code. Defaults to US if not provided."""
+ """ISO 3166 country code.
+
+ Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
+ provided.
+ """
class ConfigResidentialProxyConfig(BaseModel):
@@ -44,7 +40,10 @@ class ConfigResidentialProxyConfig(BaseModel):
"""
country: Optional[str] = None
- """ISO 3166 country code."""
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
os: Optional[Literal["windows", "macos", "android"]] = None
"""Operating system of the residential device."""
@@ -59,87 +58,17 @@ class ConfigResidentialProxyConfig(BaseModel):
class ConfigMobileProxyConfig(BaseModel):
"""Configuration for mobile proxies."""
- asn: Optional[str] = None
- """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html"""
-
- carrier: Optional[
- Literal[
- "a1",
- "aircel",
- "airtel",
- "att",
- "celcom",
- "chinamobile",
- "claro",
- "comcast",
- "cox",
- "digi",
- "dt",
- "docomo",
- "dtac",
- "etisalat",
- "idea",
- "kyivstar",
- "meo",
- "megafon",
- "mtn",
- "mtnza",
- "mts",
- "optus",
- "orange",
- "qwest",
- "reliance_jio",
- "robi",
- "sprint",
- "telefonica",
- "telstra",
- "tmobile",
- "tigo",
- "tim",
- "verizon",
- "vimpelcom",
- "vodacomza",
- "vodafone",
- "vivo",
- "zain",
- "vivabo",
- "telenormyanmar",
- "kcelljsc",
- "swisscom",
- "singtel",
- "asiacell",
- "windit",
- "cellc",
- "ooredoo",
- "drei",
- "umobile",
- "cableone",
- "proximus",
- "tele2",
- "mobitel",
- "o2",
- "bouygues",
- "free",
- "sfr",
- "digicel",
- ]
- ] = None
- """Mobile carrier."""
-
city: Optional[str] = None
- """City name (no spaces, e.g.
-
- `sanfrancisco`). If provided, `country` must also be provided.
- """
+ """Provider city alias. Mobile carrier routing can make observed geo vary."""
country: Optional[str] = None
- """ISO 3166 country code"""
+ """ISO 3166 country code.
- state: Optional[str] = None
- """Two-letter state code."""
+ If omitted, the proxy uses the global pool without country targeting.
+ """
- zip: Optional[str] = None
- """US ZIP code."""
+ state: Optional[str] = None
+ """US-only state code. Mobile carrier routing can make observed geo vary."""
class ConfigCustomProxyConfig(BaseModel):
@@ -151,6 +80,9 @@ class ConfigCustomProxyConfig(BaseModel):
port: int
"""Proxy port."""
+ has_ca_bundle: Optional[bool] = None
+ """Whether the proxy has a custom CA bundle configured."""
+
has_password: Optional[bool] = None
"""Whether the proxy has a password."""
@@ -159,23 +91,14 @@ class ConfigCustomProxyConfig(BaseModel):
Config: TypeAlias = Union[
- ConfigDatacenterProxyConfig,
- ConfigIspProxyConfig,
- ConfigResidentialProxyConfig,
- ConfigMobileProxyConfig,
- ConfigCustomProxyConfig,
+ ConfigIspProxyConfig, ConfigResidentialProxyConfig, ConfigMobileProxyConfig, ConfigCustomProxyConfig
]
class ProxyRetrieveResponse(BaseModel):
"""Configuration for routing traffic through a proxy."""
- type: Literal["datacenter", "isp", "residential", "mobile", "custom"]
- """Proxy type to use.
-
- In terms of quality for avoiding bot-detection, from best to worst: `mobile` >
- `residential` > `isp` > `datacenter`.
- """
+ type: Literal["isp", "residential", "mobile", "custom"]
id: Optional[str] = None
@@ -183,7 +106,7 @@ class ProxyRetrieveResponse(BaseModel):
"""Hostnames that should bypass the parent proxy and connect directly."""
config: Optional[Config] = None
- """Configuration specific to the selected proxy `type`."""
+ """Configuration for an ISP proxy."""
ip_address: Optional[str] = None
"""IP address that the proxy uses when making requests."""
diff --git a/src/kernel/types/proxy_update_params.py b/src/kernel/types/proxy_update_params.py
new file mode 100644
index 00000000..0db118a4
--- /dev/null
+++ b/src/kernel/types/proxy_update_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ProxyUpdateParams"]
+
+
+class ProxyUpdateParams(TypedDict, total=False):
+ name: Required[str]
+ """New proxy name.
+
+ Proxy names are trimmed and length-checked only; duplicates are allowed because
+ proxies are updated by ID, not by name.
+ """
diff --git a/src/kernel/types/proxy_update_response.py b/src/kernel/types/proxy_update_response.py
new file mode 100644
index 00000000..323a3731
--- /dev/null
+++ b/src/kernel/types/proxy_update_response.py
@@ -0,0 +1,124 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, TypeAlias
+
+from .._models import BaseModel
+
+__all__ = [
+ "ProxyUpdateResponse",
+ "Config",
+ "ConfigIspProxyConfig",
+ "ConfigResidentialProxyConfig",
+ "ConfigMobileProxyConfig",
+ "ConfigCustomProxyConfig",
+]
+
+
+class ConfigIspProxyConfig(BaseModel):
+ """Configuration for an ISP proxy."""
+
+ country: Optional[str] = None
+ """ISO 3166 country code.
+
+ Supported countries are US, GB, FR, DE, SG, and KR. Defaults to US if not
+ provided.
+ """
+
+
+class ConfigResidentialProxyConfig(BaseModel):
+ """Configuration for residential proxies."""
+
+ asn: Optional[str] = None
+ """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html"""
+
+ city: Optional[str] = None
+ """City name (no spaces, e.g.
+
+ `sanfrancisco`). If provided, `country` must also be provided.
+ """
+
+ country: Optional[str] = None
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
+
+ os: Optional[Literal["windows", "macos", "android"]] = None
+ """Operating system of the residential device."""
+
+ state: Optional[str] = None
+ """Two-letter state code."""
+
+ zip: Optional[str] = None
+ """US ZIP code."""
+
+
+class ConfigMobileProxyConfig(BaseModel):
+ """Configuration for mobile proxies."""
+
+ city: Optional[str] = None
+ """Provider city alias. Mobile carrier routing can make observed geo vary."""
+
+ country: Optional[str] = None
+ """ISO 3166 country code.
+
+ If omitted, the proxy uses the global pool without country targeting.
+ """
+
+ state: Optional[str] = None
+ """US-only state code. Mobile carrier routing can make observed geo vary."""
+
+
+class ConfigCustomProxyConfig(BaseModel):
+ """Configuration for a custom proxy (e.g., private proxy server)."""
+
+ host: str
+ """Proxy host address or IP."""
+
+ port: int
+ """Proxy port."""
+
+ has_ca_bundle: Optional[bool] = None
+ """Whether the proxy has a custom CA bundle configured."""
+
+ has_password: Optional[bool] = None
+ """Whether the proxy has a password."""
+
+ username: Optional[str] = None
+ """Username for proxy authentication."""
+
+
+Config: TypeAlias = Union[
+ ConfigIspProxyConfig, ConfigResidentialProxyConfig, ConfigMobileProxyConfig, ConfigCustomProxyConfig
+]
+
+
+class ProxyUpdateResponse(BaseModel):
+ """Configuration for routing traffic through a proxy."""
+
+ type: Literal["isp", "residential", "mobile", "custom"]
+
+ id: Optional[str] = None
+
+ bypass_hosts: Optional[List[str]] = None
+ """Hostnames that should bypass the parent proxy and connect directly."""
+
+ config: Optional[Config] = None
+ """Configuration for an ISP proxy."""
+
+ ip_address: Optional[str] = None
+ """IP address that the proxy uses when making requests."""
+
+ last_checked: Optional[datetime] = None
+ """Timestamp of the last health check performed on this proxy."""
+
+ name: Optional[str] = None
+ """Readable name of the proxy."""
+
+ protocol: Optional[Literal["http", "https"]] = None
+ """Protocol to use for the proxy connection."""
+
+ status: Optional[Literal["available", "unavailable"]] = None
+ """Current health status of the proxy."""
diff --git a/src/kernel/types/recommendation.py b/src/kernel/types/recommendation.py
new file mode 100644
index 00000000..e7d21b8e
--- /dev/null
+++ b/src/kernel/types/recommendation.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .proxy import Proxy
+from .browser import Browser
+from .._models import BaseModel
+from .evidence import Evidence
+
+__all__ = ["Recommendation"]
+
+
+class Recommendation(BaseModel):
+ browser: Browser
+ """Browser settings that can be passed directly to `POST /browsers`."""
+
+ evidence: Evidence
+
+ match_scope: Literal["exact", "host", "domain"]
+ """Specificity of knowledge matched for this recommendation.
+
+ Exact matches use knowledge for the requested target; host and domain matches
+ use broader fallback knowledge.
+ """
+
+ matched_target: str
+ """Target value that supplied the recommendation."""
+
+ proxy: Proxy
+ """Proxy recipe for the recommended browser."""
+
+ type: Literal["recommendation"]
diff --git a/src/kernel/types/recommendation_result.py b/src/kernel/types/recommendation_result.py
new file mode 100644
index 00000000..82a72d92
--- /dev/null
+++ b/src/kernel/types/recommendation_result.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import Annotated, TypeAlias
+
+from .._utils import PropertyInfo
+from .recommendation import Recommendation
+from .no_recommendation import NoRecommendation
+
+__all__ = ["RecommendationResult"]
+
+RecommendationResult: TypeAlias = Annotated[Union[Recommendation, NoRecommendation], PropertyInfo(discriminator="type")]
diff --git a/src/kernel/types/recommendation_summary.py b/src/kernel/types/recommendation_summary.py
new file mode 100644
index 00000000..5716ae60
--- /dev/null
+++ b/src/kernel/types/recommendation_summary.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from .._models import BaseModel
+from .recommendation import Recommendation
+
+__all__ = ["RecommendationSummary"]
+
+
+class RecommendationSummary(BaseModel):
+ analysis_id: str
+ """ID of the most recently requested analysis for this exact target."""
+
+ analysis_status: Literal["running", "completed", "failed", "canceled", "expired"]
+ """Lifecycle status of the most recently requested analysis for this exact target."""
+
+ last_requested_at: datetime
+ """
+ Most recent time the selected project requested an analysis for this exact
+ target.
+ """
+
+ recommendation: Optional[Recommendation] = None
+ """Recommendation produced by the latest analysis.
+
+ Null when that analysis did not produce one.
+ """
+
+ recommended_config_label: Optional[str] = None
+ """Display label for the recommended browser configuration."""
+
+ success_rate: Optional[float] = None
+ """Success rate for the recommended configuration.
+
+ Null when the latest analysis did not produce one.
+ """
+
+ target: str
+ """
+ Normalized exact target previously analyzed by the selected project, including
+ scheme, host, port, and path.
+ """
diff --git a/src/kernel/types/result.py b/src/kernel/types/result.py
new file mode 100644
index 00000000..72efaca3
--- /dev/null
+++ b/src/kernel/types/result.py
@@ -0,0 +1,119 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["Result", "Content", "ContentError"]
+
+
+class ContentError(BaseModel):
+ code: str
+ """Machine-readable retrieval failure code."""
+
+ message: str
+ """Human-readable failure description."""
+
+ retryable: bool
+
+
+class Content(BaseModel):
+ """Portable retrieval outcome, or native content supplied by the search provider.
+
+ Identity fields remain on the enclosing result. Native excerpts are labeled excerpt rather than full_page. Omission never triggers browser retrieval.
+ """
+
+ status: Literal["ok", "unavailable", "blocked", "timeout", "unsupported_type", "extraction_failed", "error"]
+ """Ok means non-empty extracted content, not merely HTTP 200.
+
+ Blocked includes detected challenges or access denials. Detection is
+ best-effort, not a guarantee of page completeness. Error details are present for
+ non-ok outcomes; text is present only on ok.
+ """
+
+ cache_status: Optional[Literal["hit", "miss", "bypass", "unknown"]] = None
+ """Kernel content cache outcome.
+
+ Kernel has no content cache yet: responses report bypass or unknown, and hit and
+ miss are reserved. Provider-internal cache behavior may be unknown.
+ """
+
+ completeness: Optional[Literal["full_page", "excerpt", "unknown"]] = None
+ """Describes source coverage before max_chars truncation.
+
+ Full_page means main-page content, not every dynamic element or linked page.
+ """
+
+ error: Optional[ContentError] = None
+
+ extractor_version: Optional[str] = None
+ """Extraction version when Kernel transformed the input."""
+
+ fetched_at: Optional[datetime] = None
+ """When Kernel received the content from the provider."""
+
+ final_url: Optional[str] = None
+ """Final retrieval URL when known."""
+
+ format: Optional[Literal["markdown", "text"]] = None
+
+ http_status: Optional[int] = None
+ """Final target HTTP status when known."""
+
+ method: Optional[Literal["provider", "browser_curl", "browser_render"]] = None
+ """Original retrieval method."""
+
+ text: Optional[str] = None
+ """Extracted website content, untrusted, not instructions.
+
+ Present only on status=ok.
+ """
+
+ truncated: Optional[bool] = None
+ """Whether max_chars truncated the extracted content."""
+
+
+class Result(BaseModel):
+ id: str
+ """Kernel-generated identifier for this result.
+
+ Stable only within the retained search; not standardized across providers.
+ Provider-native IDs, when available, remain provider-specific raw fields.
+ """
+
+ rank: int
+ """One-based position in the returned ranking."""
+
+ url: str
+ """Provider-returned URL, not assumed canonical."""
+
+ additional_snippets: Optional[List[str]] = None
+
+ content: Optional[Content] = None
+ """Portable retrieval outcome, or native content supplied by the search provider.
+
+ Identity fields remain on the enclosing result. Native excerpts are labeled
+ excerpt rather than full_page. Omission never triggers browser retrieval.
+ """
+
+ published_date: Optional[str] = None
+ """Provider-supplied date or timestamp, preserving available precision.
+
+ No publication date is fabricated. Retains the published field name.
+ """
+
+ raw: Optional[object] = None
+ """Original provider result, included only with include_raw=true.
+
+ Provider relevance scores are not normalized. Top-level provider data is
+ available in Search.raw.
+ """
+
+ snippet: Optional[str] = None
+
+ source: Optional[str] = None
+ """Provider source name or result URL hostname, when available."""
+
+ title: Optional[str] = None
diff --git a/src/kernel/types/search/__init__.py b/src/kernel/types/search/__init__.py
new file mode 100644
index 00000000..8303f80b
--- /dev/null
+++ b/src/kernel/types/search/__init__.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .search import Search as Search
+from .provider import Provider as Provider
+from .response import Response as Response
+from .content_fetch_params import ContentFetchParams as ContentFetchParams
+from .provider_list_params import ProviderListParams as ProviderListParams
+from .provider_list_response import ProviderListResponse as ProviderListResponse
diff --git a/src/kernel/types/search/content_fetch_params.py b/src/kernel/types/search/content_fetch_params.py
new file mode 100644
index 00000000..73a0294e
--- /dev/null
+++ b/src/kernel/types/search/content_fetch_params.py
@@ -0,0 +1,99 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["ContentFetchParams", "Content", "ContentBrowser"]
+
+
+class ContentFetchParams(TypedDict, total=False):
+ content: Content
+ """Defaults to source:auto when omitted."""
+
+ limit: int
+ """
+ Maximum number of search results to fetch when result_ids is omitted, starting
+ from rank 1. Mutually exclusive with result_ids.
+ """
+
+ result_ids: SequenceNotStr[str]
+ """
+ Kernel-generated IDs from the referenced retained search, in desired response
+ order. They are not provider-standard IDs. Mutually exclusive with limit.
+ """
+
+ timeout_ms: int
+ """Overall deadline across all selected results."""
+
+
+class ContentBrowser(TypedDict, total=False):
+ """Requires source=auto or source=browser in deferred retrieval."""
+
+ browser_id: str
+ """Existing browser session ID authorized for the caller and selected project.
+
+ Reuses its cookies, proxy, and browser configuration; requests follow that
+ browser's existing network access behavior, with no additional destination
+ allowlist in this endpoint. Kernel does not delete a caller-supplied browser.
+ Render mode uses a temporary tab; website activity may still change shared
+ cookies and storage. When omitted and any result needs browser retrieval, Kernel
+ creates one temporary browser for the request using the dashboard launch
+ defaults (headful, stealth, default proxy), tags it with search_id, and deletes
+ it when the request finishes. It is billed and counts toward browser concurrency
+ like any other browser. A concurrency rejection returns 429 for source=browser;
+ for source=auto, results with retained content are still returned and the rest
+ report the rejection.
+ """
+
+ mode: Literal["curl", "render"]
+ """Curl uses the browser HTTP stack without navigation or JavaScript execution.
+
+ Render navigates a temporary page and extracts from its DOM. The selected mode
+ is used for the retrieval.
+ """
+
+
+class Content(TypedDict, total=False):
+ """Defaults to source:auto when omitted."""
+
+ browser: ContentBrowser
+ """Requires source=auto or source=browser in deferred retrieval."""
+
+ format: Literal["markdown", "text"]
+
+ max_age_hours: int
+ """
+ For source=auto, maximum acceptable age of retained provider content, measured
+ from when the search received it from the provider. A value of 0 disables reuse
+ of retained content, so every result is fetched through a browser.
+ source=provider reuses retained provider content without freshness validation.
+ source=browser always fetches through a browser and does not use this age limit.
+ """
+
+ max_chars: int
+ """Per-result Unicode character limit after extraction.
+
+ Retained provider content cannot exceed what was stored at search time; such
+ results report truncated when the stored text was already truncated.
+ """
+
+ source: Literal["auto", "provider", "browser"]
+ """
+ auto uses retained provider content within max_age_hours; for deferred retrieval
+ it falls back to a Kernel browser (caller-supplied or temporary) for results
+ without it. Inline retrieval never uses a browser. provider reuses retained
+ provider content when available, without freshness validation, and never
+ provisions a browser. browser fetches each URL through a Kernel browser, either
+ caller-supplied or temporary. No option makes a new provider request. Defaults
+ to auto for both inline and deferred retrieval. Missing documents produce
+ per-result unavailable outcomes, not request failures.
+ """
+
+ timeout_ms: int
+ """Per-result deadline including capacity acquisition, retrieval, and extraction.
+
+ Also bounded by the overall request deadline.
+ """
diff --git a/src/kernel/types/search/provider.py b/src/kernel/types/search/provider.py
new file mode 100644
index 00000000..fcefff3a
--- /dev/null
+++ b/src/kernel/types/search/provider.py
@@ -0,0 +1,136 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "Provider",
+ "Content",
+ "Params",
+ "ParamsCountry",
+ "ParamsEndDate",
+ "ParamsExcludeDomains",
+ "ParamsIncludeDomains",
+ "ParamsLanguage",
+ "ParamsRecency",
+ "ParamsSafeSearch",
+ "ParamsStartDate",
+ "ProviderOptions",
+]
+
+
+class Content(BaseModel):
+ freshness_control: bool
+ """Can enforce the requested maximum content age."""
+
+ inline: bool
+ """Supports content retrieval with the search request."""
+
+ post_hoc: bool
+ """Supports content retrieval after the search completes."""
+
+
+class ParamsCountry(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class ParamsEndDate(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class ParamsExcludeDomains(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class ParamsIncludeDomains(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class ParamsLanguage(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class ParamsRecency(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class ParamsSafeSearch(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class ParamsStartDate(BaseModel):
+ support: Literal["native", "emulated", "unsupported"]
+
+ notes: Optional[str] = None
+ """Translation behavior, limitations, and precision."""
+
+
+class Params(BaseModel):
+ country: ParamsCountry
+
+ end_date: ParamsEndDate
+
+ exclude_domains: ParamsExcludeDomains
+
+ include_domains: ParamsIncludeDomains
+
+ language: ParamsLanguage
+
+ recency: ParamsRecency
+
+ safe_search: ParamsSafeSearch
+
+ start_date: ParamsStartDate
+
+
+class ProviderOptions(BaseModel):
+ schema_: Dict[str, object] = FieldInfo(alias="schema")
+ """JSON Schema for the provider-native options accepted by POST /search."""
+
+ schema_ref: str
+ """OpenAPI component name for the matching typed provider-options schema."""
+
+ examples: Optional[List[Dict[str, object]]] = None
+
+
+class Provider(BaseModel):
+ content: Content
+
+ max_results_cap: int
+
+ params: Params
+
+ provider_options: ProviderOptions
+
+ slug: str
+
+ notes: Optional[List[str]] = None
+ """
+ Provider-specific limitations, conditional filter support, and warnings about
+ search modes.
+ """
diff --git a/src/kernel/types/search/provider_list_params.py b/src/kernel/types/search/provider_list_params.py
new file mode 100644
index 00000000..908a1b17
--- /dev/null
+++ b/src/kernel/types/search/provider_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 Literal, TypedDict
+
+__all__ = ["ProviderListParams"]
+
+
+class ProviderListParams(TypedDict, total=False):
+ slug: Literal["brave", "exa", "perplexity", "context", "parallel", "valyu", "octen", "you", "tavily", "serpapi"]
+ """Optional concrete provider slug filter.
+
+ Omit to list every provider. A slug that is not listed returns an empty array.
+ """
diff --git a/src/kernel/types/browser_pool_list_response.py b/src/kernel/types/search/provider_list_response.py
similarity index 54%
rename from src/kernel/types/browser_pool_list_response.py
rename to src/kernel/types/search/provider_list_response.py
index a11c4de2..8bafd3c6 100644
--- a/src/kernel/types/browser_pool_list_response.py
+++ b/src/kernel/types/search/provider_list_response.py
@@ -3,8 +3,8 @@
from typing import List
from typing_extensions import TypeAlias
-from .browser_pool import BrowserPool
+from .provider import Provider
-__all__ = ["BrowserPoolListResponse"]
+__all__ = ["ProviderListResponse"]
-BrowserPoolListResponse: TypeAlias = List[BrowserPool]
+ProviderListResponse: TypeAlias = List[Provider]
diff --git a/src/kernel/types/search/response.py b/src/kernel/types/search/response.py
new file mode 100644
index 00000000..444e40e3
--- /dev/null
+++ b/src/kernel/types/search/response.py
@@ -0,0 +1,101 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..usage import Usage
+from ..warning import Warning
+from ..._models import BaseModel
+
+__all__ = ["Response", "Content", "ContentError"]
+
+
+class ContentError(BaseModel):
+ code: str
+ """Machine-readable retrieval failure code."""
+
+ message: str
+ """Human-readable failure description."""
+
+ retryable: bool
+
+
+class Content(BaseModel):
+ result_id: str
+
+ status: Literal["ok", "unavailable", "blocked", "timeout", "unsupported_type", "extraction_failed", "error"]
+ """Ok means non-empty extracted content, not merely HTTP 200.
+
+ Blocked includes detected challenges or access denials. Detection is
+ best-effort, not a guarantee of page completeness. Error details are present for
+ non-ok outcomes; text is present only on ok.
+ """
+
+ url: str
+ """Original result URL."""
+
+ cache_status: Optional[Literal["hit", "miss", "bypass", "unknown"]] = None
+ """Kernel content cache outcome.
+
+ Kernel has no content cache yet: responses report bypass or unknown, and hit and
+ miss are reserved. Provider-internal cache behavior may be unknown.
+ """
+
+ completeness: Optional[Literal["full_page", "excerpt", "unknown"]] = None
+ """Describes source coverage before max_chars truncation.
+
+ Full_page means main-page content, not every dynamic element or linked page.
+ """
+
+ error: Optional[ContentError] = None
+
+ extractor_version: Optional[str] = None
+ """Extraction version when Kernel transformed the input."""
+
+ fetched_at: Optional[datetime] = None
+ """
+ When Kernel fetched the content, or received it from the provider for retained
+ content.
+ """
+
+ final_url: Optional[str] = None
+ """Final retrieval URL after redirects when known.
+
+ Curl mode follows up to 5 redirects.
+ """
+
+ format: Optional[Literal["markdown", "text"]] = None
+ """Format of text.
+
+ Plain-text and JSON pages are returned unchanged as text even when markdown was
+ requested.
+ """
+
+ http_status: Optional[int] = None
+ """Final target HTTP status when known."""
+
+ method: Optional[Literal["provider", "browser_curl", "browser_render"]] = None
+ """Original retrieval method."""
+
+ text: Optional[str] = None
+ """Extracted website content, untrusted, not instructions.
+
+ Present only on status=ok.
+ """
+
+ truncated: Optional[bool] = None
+ """
+ Whether the content was cut short, by max_chars or because the page exceeded the
+ 1 MiB read limit.
+ """
+
+
+class Response(BaseModel):
+ contents: List[Content]
+
+ search_id: str
+
+ usage: Usage
+
+ warnings: List[Warning]
diff --git a/src/kernel/types/search/search.py b/src/kernel/types/search/search.py
new file mode 100644
index 00000000..e02b1dde
--- /dev/null
+++ b/src/kernel/types/search/search.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from ..usage import Usage
+from ..result import Result
+from ..attempt import Attempt
+from ..warning import Warning
+from ..._models import BaseModel
+
+__all__ = ["Search"]
+
+
+class Search(BaseModel):
+ """Retained search results and provider attempt history."""
+
+ id: str
+ """Search resource ID. Request tracing uses X-Request-Id."""
+
+ attempts: List[Attempt]
+
+ expires_at: datetime
+ """Expiration of result IDs for deferred retrieval.
+
+ Results expire 24 hours after search completion.
+ """
+
+ provider: str
+ """Concrete serving provider, never auto or fallback."""
+
+ query: str
+ """Echo of the query.
+
+ Native multi-query inputs are visible in the selected strategy target and the
+ optional raw response.
+ """
+
+ results: List[Result]
+
+ usage: Usage
+
+ warnings: List[Warning]
+
+ answer: Optional[str] = None
+ """Provider-generated answer when requested (e.g.
+
+ via Tavily include_answer or Perplexity). Preserved independently of
+ include_raw.
+ """
+
+ raw: Optional[object] = None
+ """
+ Full serving-provider response, including top-level metadata that does not
+ belong to a result. Present only with include_raw=true; untrusted provider data.
+ """
diff --git a/src/kernel/types/search_create_params.py b/src/kernel/types/search_create_params.py
new file mode 100644
index 00000000..068800ac
--- /dev/null
+++ b/src/kernel/types/search_create_params.py
@@ -0,0 +1,188 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import date
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from .._types import SequenceNotStr
+from .._utils import PropertyInfo
+from .strategy_param import StrategyParam
+
+__all__ = ["SearchCreateParams", "Content", "ContentSearchContentOptions", "ContentSearchContentOptionsBrowser"]
+
+
+class SearchCreateParams(TypedDict, total=False):
+ query: Required[str]
+ """Primary search query.
+
+ A provider-native multi-query option applies only to that provider; other
+ providers in a fallback chain receive this query.
+ """
+
+ content: Content
+ """Optional portable content retrieval.
+
+ Pass true for defaults or an options object. Omission never starts Kernel
+ browser work; provider-supplied content is still returned when available,
+ including when requested through native options. Both inline and deferred
+ retrieval use the same options schema.
+ """
+
+ country: str
+ """ISO 3166-1 alpha-2 search locale preference."""
+
+ end_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")]
+ """Inclusive publication-date upper bound; must not precede start_date.
+
+ If recency is also supplied, recency takes precedence with a warning.
+ Unsupported or approximated filtering is reported, or rejected under
+ strict_params.
+ """
+
+ exclude_domains: SequenceNotStr[str]
+ """
+ Hostname exclusions, with the same best-effort/strict behavior as
+ include_domains. Provider-specific combinations that cannot be represented are
+ reported via warnings or rejected in strict mode.
+ """
+
+ include_domains: SequenceNotStr[str]
+ """Hostname inclusion preference, matching a hostname and its subdomains.
+
+ Empty means unrestricted. Translated, emulated, or dropped with a warning
+ according to provider capability unless strict_params is true. Native boost
+ modes remain advisory and are identified in warnings.
+ """
+
+ include_raw: bool
+ """
+ Include untouched per-result payloads and the full serving-provider response in
+ raw fields. Off by default; native top-level outputs such as answer remain
+ available without it.
+ """
+
+ language: str
+ """BCP 47 search language preference."""
+
+ max_results: int
+ """Requested result count from 1 through 100.
+
+ The effective count is clamped to the serving provider's cap with a warning.
+ Effective native counts are the lower of this limit and supplied provider-native
+ count aliases. Strict mode rejects unsupported counts.
+ """
+
+ recency: Literal["hour", "day", "week", "month", "year"]
+ """Relative search window.
+
+ Takes precedence over start_date/end_date with a warning if both are set.
+ Provider-native recency behavior is retained, including documented hour-to-day
+ widening. Unsupported filters are rejected only in strict mode.
+ """
+
+ safe_search: Literal["off", "moderate", "strict"]
+ """Optional safety preference.
+
+ Omit to use provider defaults. Unsupported values are dropped with a warning
+ unless strict_params is true. A search filter is not an authorization boundary.
+ """
+
+ start_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")]
+ """Inclusive publication-date lower bound.
+
+ If recency is also supplied, recency takes precedence with a warning. Provider
+ date semantics, precision, and unsupported filters are reported; unknown source
+ dates are not fabricated or universally post-filtered.
+ """
+
+ strategy: StrategyParam
+ """Omitted strategy defaults to auto."""
+
+ strict_params: bool
+ """
+ When false, unsupported portable parameters are omitted and approximations are
+ described in warnings. When true, every supplied portable parameter must be
+ honored exactly. Requests that cannot be served with those parameters are
+ rejected. This does not guarantee identical rankings or document timestamps
+ across indexes. Authentication and project isolation are always enforced.
+ """
+
+ timeout_ms: int
+ """
+ Overall deadline across search attempts and inline retrieval. No new attempt
+ starts after the deadline. Completed search results survive inline retrieval
+ timeouts.
+ """
+
+
+class ContentSearchContentOptionsBrowser(TypedDict, total=False):
+ """Requires source=auto or source=browser in deferred retrieval."""
+
+ browser_id: str
+ """Existing browser session ID authorized for the caller and selected project.
+
+ Reuses its cookies, proxy, and browser configuration; requests follow that
+ browser's existing network access behavior, with no additional destination
+ allowlist in this endpoint. Kernel does not delete a caller-supplied browser.
+ Render mode uses a temporary tab; website activity may still change shared
+ cookies and storage. When omitted and any result needs browser retrieval, Kernel
+ creates one temporary browser for the request using the dashboard launch
+ defaults (headful, stealth, default proxy), tags it with search_id, and deletes
+ it when the request finishes. It is billed and counts toward browser concurrency
+ like any other browser. A concurrency rejection returns 429 for source=browser;
+ for source=auto, results with retained content are still returned and the rest
+ report the rejection.
+ """
+
+ mode: Literal["curl", "render"]
+ """Curl uses the browser HTTP stack without navigation or JavaScript execution.
+
+ Render navigates a temporary page and extracts from its DOM. The selected mode
+ is used for the retrieval.
+ """
+
+
+class ContentSearchContentOptions(TypedDict, total=False):
+ browser: ContentSearchContentOptionsBrowser
+ """Requires source=auto or source=browser in deferred retrieval."""
+
+ format: Literal["markdown", "text"]
+
+ max_age_hours: int
+ """
+ For source=auto, maximum acceptable age of retained provider content, measured
+ from when the search received it from the provider. A value of 0 disables reuse
+ of retained content, so every result is fetched through a browser.
+ source=provider reuses retained provider content without freshness validation.
+ source=browser always fetches through a browser and does not use this age limit.
+ """
+
+ max_chars: int
+ """Per-result Unicode character limit after extraction.
+
+ Retained provider content cannot exceed what was stored at search time; such
+ results report truncated when the stored text was already truncated.
+ """
+
+ source: Literal["auto", "provider", "browser"]
+ """
+ auto uses retained provider content within max_age_hours; for deferred retrieval
+ it falls back to a Kernel browser (caller-supplied or temporary) for results
+ without it. Inline retrieval never uses a browser. provider reuses retained
+ provider content when available, without freshness validation, and never
+ provisions a browser. browser fetches each URL through a Kernel browser, either
+ caller-supplied or temporary. No option makes a new provider request. Defaults
+ to auto for both inline and deferred retrieval. Missing documents produce
+ per-result unavailable outcomes, not request failures.
+ """
+
+ timeout_ms: int
+ """Per-result deadline including capacity acquisition, retrieval, and extraction.
+
+ Also bounded by the overall request deadline.
+ """
+
+
+Content: TypeAlias = Union[Literal[True], ContentSearchContentOptions]
diff --git a/src/kernel/types/shared/browser_viewport.py b/src/kernel/types/shared/browser_viewport.py
index c53505be..bfb82aa4 100644
--- a/src/kernel/types/shared/browser_viewport.py
+++ b/src/kernel/types/shared/browser_viewport.py
@@ -14,7 +14,7 @@ class BrowserViewport(BaseModel):
For GPU images, the default is 1920x1080@60.
Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include:
- 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60.
For GPU images, recommended presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
Viewports outside this list may exhibit unstable live view or recording behavior.
@@ -23,13 +23,14 @@ class BrowserViewport(BaseModel):
"""
height: int
- """Browser window height in pixels."""
+ """Browser window height in pixels. Any positive integer is accepted."""
width: int
- """Browser window width in pixels."""
+ """Browser window width in pixels. Any positive integer is accepted."""
refresh_rate: Optional[int] = None
"""Display refresh rate in Hz.
- If omitted, automatically determined from width and height.
+ Any positive integer is accepted; if omitted, automatically determined from
+ width and height.
"""
diff --git a/src/kernel/types/shared_params/browser_viewport.py b/src/kernel/types/shared_params/browser_viewport.py
index 2290f930..5b03b2f9 100644
--- a/src/kernel/types/shared_params/browser_viewport.py
+++ b/src/kernel/types/shared_params/browser_viewport.py
@@ -14,7 +14,7 @@ class BrowserViewport(TypedDict, total=False):
For GPU images, the default is 1920x1080@60.
Arbitrary viewport dimensions and refresh rates are accepted.
Known-good presets include:
- 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60, 768x1024@60, 390x844@60.
For GPU images, recommended presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768, 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
Viewports outside this list may exhibit unstable live view or recording behavior.
@@ -23,13 +23,14 @@ class BrowserViewport(TypedDict, total=False):
"""
height: Required[int]
- """Browser window height in pixels."""
+ """Browser window height in pixels. Any positive integer is accepted."""
width: Required[int]
- """Browser window width in pixels."""
+ """Browser window width in pixels. Any positive integer is accepted."""
refresh_rate: int
"""Display refresh rate in Hz.
- If omitted, automatically determined from width and height.
+ Any positive integer is accepted; if omitted, automatically determined from
+ width and height.
"""
diff --git a/src/kernel/types/strategy_param.py b/src/kernel/types/strategy_param.py
new file mode 100644
index 00000000..d755a092
--- /dev/null
+++ b/src/kernel/types/strategy_param.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Iterable
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+from .provider_target_param import ProviderTargetParam
+
+__all__ = ["StrategyParam", "SearchAutoStrategy", "SearchPinnedStrategy", "SearchFallbackStrategy"]
+
+
+class SearchAutoStrategy(TypedDict, total=False):
+ type: Required[Literal["auto"]]
+ """Let Kernel choose an eligible provider by capability fit."""
+
+ fallback_on: List[Literal["error", "timeout", "empty"]]
+ """
+ Conditions that advance to the next provider under auto routing or an explicit
+ providers chain. Ignored when provider pins a single provider. error means a
+ retryable provider failure, including rate limiting, not invalid caller input or
+ caller quotas. empty means zero results after required filtering. An empty list
+ disables fallback. If every attempt is empty or fails, the response is the first
+ valid empty response with the full attempt trail, or a 502 if none succeeded.
+ """
+
+ provider_options: Iterable[ProviderTargetParam]
+ """
+ Provider targets available to auto routing, each paired with typed native
+ options. Provider names must be unique.
+ """
+
+
+class SearchPinnedStrategy(TypedDict, total=False):
+ provider: Required[ProviderTargetParam]
+ """Provider name paired with its typed native options."""
+
+ type: Required[Literal["pinned"]]
+ """Use exactly the selected provider with no cross-provider fallback."""
+
+
+class SearchFallbackStrategy(TypedDict, total=False):
+ providers: Required[Iterable[ProviderTargetParam]]
+ """Ordered provider targets. Provider names must be unique."""
+
+ type: Required[Literal["fallback"]]
+ """Try providers in order and advance when fallback_on matches the outcome."""
+
+ fallback_on: List[Literal["error", "timeout", "empty"]]
+ """
+ Conditions that advance to the next provider under auto routing or an explicit
+ providers chain. Ignored when provider pins a single provider. error means a
+ retryable provider failure, including rate limiting, not invalid caller input or
+ caller quotas. empty means zero results after required filtering. An empty list
+ disables fallback. If every attempt is empty or fails, the response is the first
+ valid empty response with the full attempt trail, or a 502 if none succeeded.
+ """
+
+
+StrategyParam: TypeAlias = Union[SearchAutoStrategy, SearchPinnedStrategy, SearchFallbackStrategy]
diff --git a/src/kernel/types/tags.py b/src/kernel/types/tags.py
new file mode 100644
index 00000000..988894da
--- /dev/null
+++ b/src/kernel/types/tags.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["Tags"]
+
+Tags: TypeAlias = Dict[str, str]
diff --git a/src/kernel/types/tags_param.py b/src/kernel/types/tags_param.py
new file mode 100644
index 00000000..9e9e5cba
--- /dev/null
+++ b/src/kernel/types/tags_param.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["TagsParam"]
+
+TagsParam: TypeAlias = Dict[str, str]
diff --git a/src/kernel/types/target.py b/src/kernel/types/target.py
new file mode 100644
index 00000000..6950d513
--- /dev/null
+++ b/src/kernel/types/target.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["Target"]
+
+
+class Target(BaseModel):
+ domain: str
+ """Registrable domain."""
+
+ host: str
+ """Full hostname, including subdomain."""
+
+ normalized: str
+ """Exact normalized scheme, host, port, and path used for lookup."""
diff --git a/src/kernel/types/telemetry/__init__.py b/src/kernel/types/telemetry/__init__.py
new file mode 100644
index 00000000..e0a465df
--- /dev/null
+++ b/src/kernel/types/telemetry/__init__.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .otlp_destination import OtlpDestination as OtlpDestination
+from .destination_list_params import DestinationListParams as DestinationListParams
+from .destination_create_params import DestinationCreateParams as DestinationCreateParams
+from .destination_update_params import DestinationUpdateParams as DestinationUpdateParams
diff --git a/src/kernel/types/telemetry/destination_create_params.py b/src/kernel/types/telemetry/destination_create_params.py
new file mode 100644
index 00000000..5d10b71f
--- /dev/null
+++ b/src/kernel/types/telemetry/destination_create_params.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Required, TypedDict
+
+__all__ = ["DestinationCreateParams"]
+
+
+class DestinationCreateParams(TypedDict, total=False):
+ endpoint: Required[str]
+ """Base endpoint of the OTLP/HTTP collector, without a signal path.
+
+ Kernel appends the signal path itself, so pass `https://api.honeycomb.io` rather
+ than `https://api.honeycomb.io/v1/logs`. If your provider's docs give you a
+ signal-specific URL, drop the trailing `/v1/logs`, `/v1/traces`, or
+ `/v1/metrics` — an endpoint that already carries one is rejected.
+
+ Must be http or https, must resolve to a public address, and must carry no query
+ string or fragment. Examples: `https://api.honeycomb.io`,
+ `https://otlp-gateway-prod-us-east-0.grafana.net/otlp`,
+ `https://otlp.datadoghq.com` (Datadog's OTLP intake for US1, not its logs
+ intake).
+ """
+
+ name: Required[str]
+ """Unique within the organization."""
+
+ description: str
+
+ headers: Dict[str, str]
+ """Headers sent with each export request, typically an ingestion key.
+
+ Encrypted at rest and returned redacted. Names and values must be valid HTTP
+ header tokens, and the names and values together cannot exceed 8192 bytes. Names
+ are matched case-insensitively and stored canonicalized, so supplying two
+ spellings of one header is rejected.
+ """
diff --git a/src/kernel/types/telemetry/destination_list_params.py b/src/kernel/types/telemetry/destination_list_params.py
new file mode 100644
index 00000000..fa7a1dbe
--- /dev/null
+++ b/src/kernel/types/telemetry/destination_list_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["DestinationListParams"]
+
+
+class DestinationListParams(TypedDict, total=False):
+ limit: int
+ """Limit the number of destinations to return."""
+
+ name: str
+ """Exact-match filter on destination name using the database collation.
+
+ In production, matching is case- and accent-insensitive.
+ """
+
+ offset: int
+ """Offset the number of destinations to return."""
+
+ query: str
+ """Case-insensitive substring match against destination name or endpoint.
+
+ IDs match by exact value.
+ """
diff --git a/src/kernel/types/telemetry/destination_update_params.py b/src/kernel/types/telemetry/destination_update_params.py
new file mode 100644
index 00000000..266b8029
--- /dev/null
+++ b/src/kernel/types/telemetry/destination_update_params.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import TypedDict
+
+__all__ = ["DestinationUpdateParams"]
+
+
+class DestinationUpdateParams(TypedDict, total=False):
+ description: str
+
+ endpoint: str
+ """Base endpoint of the OTLP/HTTP collector, without a signal path.
+
+ Same rules as on create.
+ """
+
+ headers: Dict[str, Optional[str]]
+ """Edits stored headers key by key rather than replacing the map.
+
+ A string value adds or replaces that header, `null` deletes it, and any key you
+ omit is left as it is. Names are matched case-insensitively, so `authorization`
+ replaces a stored `Authorization` rather than adding a second entry. This is the
+ credential rotation path; sessions already exporting pick up the new values
+ without restarting. Names and values must be valid HTTP header tokens, and the
+ names and values together cannot exceed 8192 bytes.
+ """
+
+ name: str
diff --git a/src/kernel/types/telemetry/otlp_destination.py b/src/kernel/types/telemetry/otlp_destination.py
new file mode 100644
index 00000000..293c2497
--- /dev/null
+++ b/src/kernel/types/telemetry/otlp_destination.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["OtlpDestination"]
+
+
+class OtlpDestination(BaseModel):
+ """An OTLP endpoint to export browser session telemetry to.
+
+ Reference one from `telemetry.export.otlp.destination` when creating a browser to export that session's captured telemetry to it.
+ """
+
+ id: str
+
+ consecutive_failures: int
+ """
+ Failed deliveries since the last success, as observed by the relay process that
+ wrote the latest outcome. Zero means the most recently recorded outcome
+ succeeded.
+ """
+
+ created_at: datetime
+
+ endpoint: str
+ """OTLP/HTTP endpoint telemetry is sent to."""
+
+ headers: Dict[str, str]
+ """Headers sent with each export request.
+
+ Names are returned in canonical form (`Authorization`, not `authorization`).
+ Non-dashboard reads return values redacted as empty strings, so the keys are
+ visible but the credentials are not. Dashboard reads return the stored values.
+ """
+
+ name: str
+ """Unique within the organization.
+
+ Usable in place of the ID when selecting a destination, so it cannot be shaped
+ like an ID.
+ """
+
+ updated_at: datetime
+
+ description: Optional[str] = None
+
+ last_error: Optional[str] = None
+ """Sanitized class of the delivery failure recorded at `last_error_at`.
+
+ It is retained after a later success, so its presence does not mean the
+ destination is currently failing. Response bodies, endpoint URLs, credentials,
+ and raw transport errors are never returned.
+ """
+
+ last_error_at: Optional[datetime] = None
+ """Timestamp of the most recent failed delivery.
+
+ It is retained after a later success, so it can predate `last_export_at`. Read
+ `consecutive_failures` to tell whether the destination is currently failing.
+ """
+
+ last_export_at: Optional[datetime] = None
+ """Timestamp of the most recent successful delivery. Moves only on success."""
diff --git a/src/kernel/types/usage.py b/src/kernel/types/usage.py
new file mode 100644
index 00000000..5ca1c30c
--- /dev/null
+++ b/src/kernel/types/usage.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["Usage"]
+
+
+class Usage(BaseModel):
+ content_fetches: int
+ """
+ Number of result URLs for which a Kernel browser retrieval received a response
+ from the target, excluding cache-only hits.
+ """
+
+ results_count: int
+ """
+ Number of result entries returned, including failed entries on the contents
+ endpoint.
+ """
+
+ cost: Optional[float] = None
+ """Total customer charge in USD when billing data is available."""
diff --git a/src/kernel/types/vault.py b/src/kernel/types/vault.py
new file mode 100644
index 00000000..210edfbe
--- /dev/null
+++ b/src/kernel/types/vault.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["Vault"]
+
+
+class Vault(BaseModel):
+ id: str
+
+ created_at: datetime
+
+ name: str
+ """Immutable name assigned when the vault is created."""
+
+ updated_at: datetime
diff --git a/src/kernel/types/vault_encryption_key.py b/src/kernel/types/vault_encryption_key.py
new file mode 100644
index 00000000..6dc53906
--- /dev/null
+++ b/src/kernel/types/vault_encryption_key.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["VaultEncryptionKey", "Jwk"]
+
+
+class Jwk(BaseModel):
+ """P-256 public key in JWK form."""
+
+ crv: Literal["P-256"]
+
+ kty: Literal["EC"]
+
+ x: str
+ """Base64url-encoded x coordinate."""
+
+ y: str
+ """Base64url-encoded y coordinate."""
+
+
+class VaultEncryptionKey(BaseModel):
+ """Public key for encrypted_value on credential fields.
+
+ Use this only if you run your own credential collection web app and want values encrypted in the browser, sent to your backend still encrypted, and forwarded to Kernel's API still encrypted. In every other case, including server-side code that already holds the plaintext, use value. Each vault has its own key; a value encrypted for one vault is rejected by every other vault. The key is created on first request and stays the same for the vault's lifetime, so it may be cached.
+ """
+
+ alg: Literal["ECDH-ES"]
+ """JWE key management algorithm."""
+
+ enc: Literal["A256GCM"]
+ """JWE content encryption algorithm."""
+
+ jwk: Jwk
+ """P-256 public key in JWK form."""
+
+ kid: str
+ """Key ID. Set it as the kid protected header of every encrypted_value."""
diff --git a/src/kernel/types/vault_list_params.py b/src/kernel/types/vault_list_params.py
new file mode 100644
index 00000000..7a0848df
--- /dev/null
+++ b/src/kernel/types/vault_list_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["VaultListParams"]
+
+
+class VaultListParams(TypedDict, total=False):
+ limit: int
+
+ offset: int
+
+ query: str
+ """Case-insensitive substring match against vault name. IDs match by exact value."""
diff --git a/src/kernel/types/vault_provider_config.py b/src/kernel/types/vault_provider_config.py
new file mode 100644
index 00000000..03b0a427
--- /dev/null
+++ b/src/kernel/types/vault_provider_config.py
@@ -0,0 +1,64 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from .._utils import PropertyInfo
+from .._models import BaseModel
+
+__all__ = ["VaultProviderConfig", "VaultLinkProviderConfig", "VaultAgentCardProviderConfig"]
+
+
+class VaultLinkProviderConfig(BaseModel):
+ """Response schema for a Link configuration, without secret credentials.
+
+ Kernel generates the ID and timestamps. Configuration creation uses VaultLinkProviderConfigRequest.
+ """
+
+ id: str
+
+ client_id: str
+ """OAuth client identity; immutable. Secret credentials are never returned."""
+
+ created_at: datetime
+
+ name: str
+ """Unique within the organization."""
+
+ provider: Literal["link"]
+
+ updated_at: datetime
+
+ publishable_key: Optional[str] = None
+ """Stripe publishable key sent to Link when refreshing and revoking wallet grants.
+
+ Omitted when not configured.
+ """
+
+
+class VaultAgentCardProviderConfig(BaseModel):
+ """Response schema for an AgentCard configuration, without secret credentials.
+
+ Kernel generates the ID and timestamps and introspects test_mode from the credentials. Configuration creation uses VaultAgentCardProviderConfigRequest.
+ """
+
+ id: str
+
+ client_id: str
+
+ created_at: datetime
+
+ name: str
+
+ provider: Literal["agentcard"]
+
+ test_mode: bool
+ """Introspected mode of the selected credential; true means sandbox objects."""
+
+ updated_at: datetime
+
+
+VaultProviderConfig: TypeAlias = Annotated[
+ Union[VaultLinkProviderConfig, VaultAgentCardProviderConfig], PropertyInfo(discriminator="provider")
+]
diff --git a/src/kernel/types/vault_provider_config_create_params.py b/src/kernel/types/vault_provider_config_create_params.py
new file mode 100644
index 00000000..bbef1ed7
--- /dev/null
+++ b/src/kernel/types/vault_provider_config_create_params.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+__all__ = [
+ "VaultProviderConfigCreateParams",
+ "VaultLinkProviderConfigRequest",
+ "VaultLinkProviderConfigRequestCredentials",
+ "VaultAgentCardProviderConfigRequest",
+ "VaultAgentCardProviderConfigRequestCredentials",
+]
+
+
+class VaultLinkProviderConfigRequest(TypedDict, total=False):
+ credentials: Required[VaultLinkProviderConfigRequestCredentials]
+
+ name: Required[str]
+ """Unique within the organization."""
+
+ provider: Required[Literal["link"]]
+
+
+class VaultLinkProviderConfigRequestCredentials(TypedDict, total=False):
+ client_id: Required[str]
+
+ client_secret: Required[str]
+
+ publishable_key: str
+ """Stripe publishable key for the account that owns the Link OAuth client.
+
+ Link requires it as the bearer credential when Kernel refreshes or revokes
+ imported wallet grants; without it, those wallets stop working when the imported
+ access token expires.
+ """
+
+
+class VaultAgentCardProviderConfigRequest(TypedDict, total=False):
+ credentials: Required[VaultAgentCardProviderConfigRequestCredentials]
+
+ name: Required[str]
+ """Unique within the organization."""
+
+ provider: Required[Literal["agentcard"]]
+
+
+class VaultAgentCardProviderConfigRequestCredentials(TypedDict, total=False):
+ client_id: Required[str]
+
+ client_secret: Required[str]
+
+
+VaultProviderConfigCreateParams: TypeAlias = Union[VaultLinkProviderConfigRequest, VaultAgentCardProviderConfigRequest]
diff --git a/src/kernel/types/vault_provider_config_list_params.py b/src/kernel/types/vault_provider_config_list_params.py
new file mode 100644
index 00000000..27065f97
--- /dev/null
+++ b/src/kernel/types/vault_provider_config_list_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["VaultProviderConfigListParams"]
+
+
+class VaultProviderConfigListParams(TypedDict, total=False):
+ limit: int
+
+ offset: int
diff --git a/src/kernel/types/vault_provider_config_update_params.py b/src/kernel/types/vault_provider_config_update_params.py
new file mode 100644
index 00000000..b4c18d20
--- /dev/null
+++ b/src/kernel/types/vault_provider_config_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_extensions import TypedDict
+
+__all__ = ["VaultProviderConfigUpdateParams", "Credentials"]
+
+
+class VaultProviderConfigUpdateParams(TypedDict, total=False):
+ credentials: Credentials
+ """Fields to update.
+
+ Omitted credentials are left unchanged. A rejected update leaves existing
+ credentials unchanged.
+ """
+
+ name: str
+ """Unique within the organization."""
+
+
+class Credentials(TypedDict, total=False):
+ """Fields to update.
+
+ Omitted credentials are left unchanged. A rejected update leaves existing credentials unchanged.
+ """
+
+ client_secret: str
+
+ publishable_key: str
+ """Link configurations only.
+
+ Stripe publishable key sent to Link when refreshing and revoking wallet grants.
+ """
diff --git a/src/kernel/types/vault_reference.py b/src/kernel/types/vault_reference.py
new file mode 100644
index 00000000..f22e7de8
--- /dev/null
+++ b/src/kernel/types/vault_reference.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["VaultReference"]
+
+
+class VaultReference(BaseModel):
+ """Reference to a project-scoped vault. Provide exactly one of id or name."""
+
+ id: Optional[str] = None
+
+ name: Optional[str] = None
diff --git a/src/kernel/types/vault_reference_param.py b/src/kernel/types/vault_reference_param.py
new file mode 100644
index 00000000..3e8fe9ac
--- /dev/null
+++ b/src/kernel/types/vault_reference_param.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__ = ["VaultReferenceParam"]
+
+
+class VaultReferenceParam(TypedDict, total=False):
+ """Reference to a project-scoped vault. Provide exactly one of id or name."""
+
+ id: str
+
+ name: str
diff --git a/src/kernel/types/vault_upsert_params.py b/src/kernel/types/vault_upsert_params.py
new file mode 100644
index 00000000..84be182b
--- /dev/null
+++ b/src/kernel/types/vault_upsert_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__ = ["VaultUpsertParams"]
+
+
+class VaultUpsertParams(TypedDict, total=False):
+ name: Required[str]
+ """Immutable name used to create or retrieve the vault."""
diff --git a/src/kernel/types/vaults/__init__.py b/src/kernel/types/vaults/__init__.py
new file mode 100644
index 00000000..d72d730c
--- /dev/null
+++ b/src/kernel/types/vaults/__init__.py
@@ -0,0 +1,130 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .vault_item import VaultItem as VaultItem
+from .vault_item_event import VaultItemEvent as VaultItemEvent
+from .kernel_card_state import KernelCardState as KernelCardState
+from .vault_item_action import VaultItemAction as VaultItemAction
+from .item_events_params import ItemEventsParams as ItemEventsParams
+from .item_list_response import ItemListResponse as ItemListResponse
+from .item_update_params import ItemUpdateParams as ItemUpdateParams
+from .item_upsert_params import ItemUpsertParams as ItemUpsertParams
+from .vault_card_aliases import VaultCardAliases as VaultCardAliases
+from .kernel_wallet_state import KernelWalletState as KernelWalletState
+from .card_vault_item_spec import CardVaultItemSpec as CardVaultItemSpec
+from .item_events_response import ItemEventsResponse as ItemEventsResponse
+from .item_retrieve_params import ItemRetrieveParams as ItemRetrieveParams
+from .vault_payment_method import VaultPaymentMethod as VaultPaymentMethod
+from .card_vault_item_state import CardVaultItemState as CardVaultItemState
+from .credential_vault_item import CredentialVaultItem as CredentialVaultItem
+from .vault_fill_field_param import VaultFillFieldParam as VaultFillFieldParam
+from .wallet_vault_item_spec import WalletVaultItemSpec as WalletVaultItemSpec
+from .vault_fill_field_result import VaultFillFieldResult as VaultFillFieldResult
+from .wallet_vault_item_state import WalletVaultItemState as WalletVaultItemState
+from .one_password_oauth_action import OnePasswordOAuthAction as OnePasswordOAuthAction
+from .card_vault_item_spec_param import CardVaultItemSpecParam as CardVaultItemSpecParam
+from .credential_vault_item_spec import CredentialVaultItemSpec as CredentialVaultItemSpec
+from .vault_webmcp_binding_param import VaultWebmcpBindingParam as VaultWebmcpBindingParam
+from .credential_vault_field_type import CredentialVaultFieldType as CredentialVaultFieldType
+from .credential_vault_item_state import CredentialVaultItemState as CredentialVaultItemState
+from .kernel_card_vault_item_spec import KernelCardVaultItemSpec as KernelCardVaultItemSpec
+from .agentcard_prepared_processor import AgentcardPreparedProcessor as AgentcardPreparedProcessor
+from .credential_collection_action import CredentialCollectionAction as CredentialCollectionAction
+from .credential_vault_field_state import CredentialVaultFieldState as CredentialVaultFieldState
+from .vault_checkout_context_param import VaultCheckoutContextParam as VaultCheckoutContextParam
+from .credential_account_vault_item import CredentialAccountVaultItem as CredentialAccountVaultItem
+from .item_perform_operation_params import ItemPerformOperationParams as ItemPerformOperationParams
+from .kernel_wallet_vault_item_spec import KernelWalletVaultItemSpec as KernelWalletVaultItemSpec
+from .vault_item_operation_response import VaultItemOperationResponse as VaultItemOperationResponse
+from .agentcard_checkout_preparation import AgentcardCheckoutPreparation as AgentcardCheckoutPreparation
+from .agentcard_checkout_authorization import AgentcardCheckoutAuthorization as AgentcardCheckoutAuthorization
+from .fill_vault_item_operation_result import FillVaultItemOperationResult as FillVaultItemOperationResult
+from .credential_vault_field_definition import CredentialVaultFieldDefinition as CredentialVaultFieldDefinition
+from .kernel_card_vault_item_spec_param import KernelCardVaultItemSpecParam as KernelCardVaultItemSpecParam
+from .kernel_credential_vault_item_spec import KernelCredentialVaultItemSpec as KernelCredentialVaultItemSpec
+from .credential_vault_field_input_param import CredentialVaultFieldInputParam as CredentialVaultFieldInputParam
+from .kernel_credential_vault_item_state import KernelCredentialVaultItemState as KernelCredentialVaultItemState
+from .credential_vault_field_update_param import CredentialVaultFieldUpdateParam as CredentialVaultFieldUpdateParam
+from .credential_vault_item_request_param import CredentialVaultItemRequestParam as CredentialVaultItemRequestParam
+from .kernel_wallet_vault_item_spec_param import KernelWalletVaultItemSpecParam as KernelWalletVaultItemSpecParam
+from .managed_auth_credential_vault_field import ManagedAuthCredentialVaultField as ManagedAuthCredentialVaultField
+from .one_password_credential_account_spec import OnePasswordCredentialAccountSpec as OnePasswordCredentialAccountSpec
+from .one_password_credential_account_state import (
+ OnePasswordCredentialAccountState as OnePasswordCredentialAccountState,
+)
+from .credential_vault_item_spec_input_param import (
+ CredentialVaultItemSpecInputParam as CredentialVaultItemSpecInputParam,
+)
+from .credential_vault_item_spec_update_param import (
+ CredentialVaultItemSpecUpdateParam as CredentialVaultItemSpecUpdateParam,
+)
+from .fill_vault_item_operation_request_param import (
+ FillVaultItemOperationRequestParam as FillVaultItemOperationRequestParam,
+)
+from .managed_auth_credential_vault_item_spec import (
+ ManagedAuthCredentialVaultItemSpec as ManagedAuthCredentialVaultItemSpec,
+)
+from .one_password_credential_vault_item_spec import (
+ OnePasswordCredentialVaultItemSpec as OnePasswordCredentialVaultItemSpec,
+)
+from .managed_auth_credential_vault_item_state import (
+ ManagedAuthCredentialVaultItemState as ManagedAuthCredentialVaultItemState,
+)
+from .one_password_credential_vault_item_state import (
+ OnePasswordCredentialVaultItemState as OnePasswordCredentialVaultItemState,
+)
+from .webmcp_invoke_vault_item_operation_result import (
+ WebmcpInvokeVaultItemOperationResult as WebmcpInvokeVaultItemOperationResult,
+)
+from .collect_vault_item_operation_request_param import (
+ CollectVaultItemOperationRequestParam as CollectVaultItemOperationRequestParam,
+)
+from .credential_vault_item_update_request_param import (
+ CredentialVaultItemUpdateRequestParam as CredentialVaultItemUpdateRequestParam,
+)
+from .one_password_credential_account_spec_param import (
+ OnePasswordCredentialAccountSpecParam as OnePasswordCredentialAccountSpecParam,
+)
+from .credential_account_vault_item_request_param import (
+ CredentialAccountVaultItemRequestParam as CredentialAccountVaultItemRequestParam,
+)
+from .authorize_vault_item_operation_request_param import (
+ AuthorizeVaultItemOperationRequestParam as AuthorizeVaultItemOperationRequestParam,
+)
+from .credential_vault_field_definition_input_param import (
+ CredentialVaultFieldDefinitionInputParam as CredentialVaultFieldDefinitionInputParam,
+)
+from .kernel_credential_vault_item_spec_input_param import (
+ KernelCredentialVaultItemSpecInputParam as KernelCredentialVaultItemSpecInputParam,
+)
+from .one_password_fill_vault_item_operation_result import (
+ OnePasswordFillVaultItemOperationResult as OnePasswordFillVaultItemOperationResult,
+)
+from .webmcp_invoke_vault_item_operation_request_param import (
+ WebmcpInvokeVaultItemOperationRequestParam as WebmcpInvokeVaultItemOperationRequestParam,
+)
+from .managed_auth_credential_vault_item_spec_input_param import (
+ ManagedAuthCredentialVaultItemSpecInputParam as ManagedAuthCredentialVaultItemSpecInputParam,
+)
+from .one_password_credential_vault_item_spec_input_param import (
+ OnePasswordCredentialVaultItemSpecInputParam as OnePasswordCredentialVaultItemSpecInputParam,
+)
+from .prepare_checkout_vault_item_operation_request_param import (
+ PrepareCheckoutVaultItemOperationRequestParam as PrepareCheckoutVaultItemOperationRequestParam,
+)
+from .one_password_fill_vault_item_operation_request_param import (
+ OnePasswordFillVaultItemOperationRequestParam as OnePasswordFillVaultItemOperationRequestParam,
+)
+from .confirm_transaction_vault_item_operation_request_param import (
+ ConfirmTransactionVaultItemOperationRequestParam as ConfirmTransactionVaultItemOperationRequestParam,
+)
+from .one_password_recover_vault_item_operation_request_param import (
+ OnePasswordRecoverVaultItemOperationRequestParam as OnePasswordRecoverVaultItemOperationRequestParam,
+)
+from .restart_verification_vault_item_operation_request_param import (
+ RestartVerificationVaultItemOperationRequestParam as RestartVerificationVaultItemOperationRequestParam,
+)
+from .one_password_request_access_vault_item_operation_request_param import (
+ OnePasswordRequestAccessVaultItemOperationRequestParam as OnePasswordRequestAccessVaultItemOperationRequestParam,
+)
diff --git a/src/kernel/types/vaults/agentcard_checkout_authorization.py b/src/kernel/types/vaults/agentcard_checkout_authorization.py
new file mode 100644
index 00000000..2a0dc815
--- /dev/null
+++ b/src/kernel/types/vaults/agentcard_checkout_authorization.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["AgentcardCheckoutAuthorization"]
+
+
+class AgentcardCheckoutAuthorization(BaseModel):
+ """The in-flight or most recent checkout authorization.
+
+ Present while a checkout is pending approval and after it settles.
+ """
+
+ id: str
+
+ amount_cents: int
+
+ created_at: datetime
+
+ currency: str
+
+ merchant: str
+
+ psp: str
+
+ status: Literal["awaiting_approval", "approved", "declined", "expired"]
+
+ actual_cents: Optional[int] = None
+
+ amount: Optional[str] = None
+ """Display amount shown on the approval screen."""
+
+ amount_authority: Optional[Literal["display_only", "stripe_payment_intent"]] = None
+
+ amount_verified: Optional[bool] = None
+
+ approval_url: Optional[str] = None
+
+ browser_id: Optional[str] = None
+ """Browser session that submitted the checkout."""
+
+ charged_amount_cents: Optional[int] = None
+
+ charged_currency: Optional[str] = None
+
+ charged_kind: Optional[Literal["captured", "authorized", "none"]] = None
+
+ expected_cents: Optional[int] = None
+
+ expires_at: Optional[datetime] = None
+
+ psp_error_code: Optional[str] = None
+
+ reason: Optional[str] = None
+
+ replay_attempted: Optional[bool] = None
+
+ replay_delivered: Optional[bool] = None
+ """Whether the processor response was delivered to the browser."""
+
+ replay_status: Optional[int] = None
+ """HTTP status of the replayed processor response."""
diff --git a/src/kernel/types/vaults/agentcard_checkout_preparation.py b/src/kernel/types/vaults/agentcard_checkout_preparation.py
new file mode 100644
index 00000000..419f3fdd
--- /dev/null
+++ b/src/kernel/types/vaults/agentcard_checkout_preparation.py
@@ -0,0 +1,46 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .agentcard_prepared_processor import AgentcardPreparedProcessor
+
+__all__ = ["AgentcardCheckoutPreparation"]
+
+
+class AgentcardCheckoutPreparation(BaseModel):
+ """One-use processor-bound checkout preparation.
+
+ Keep the approval page open through device handoff, including Adyen encryption. The amount is declared by the caller and does not constrain the merchant's eventual charge. Adyen device approval and browser Authorised responses are not capture or fulfillment evidence.
+ """
+
+ browser_id: str
+
+ created_at: datetime
+
+ environment: Literal["production", "sandbox", "shared"]
+
+ merchant_origin: str
+
+ psp: AgentcardPreparedProcessor
+
+ status: Literal["creating", "awaiting_approval", "ready", "consumed", "cancelled", "expired", "unknown"]
+ """
+ Preparation consumed means egress claimed the preparation and it cannot be
+ reused. It does not mean the attempt settled. Use the enclosing item's status as
+ the lifecycle indicator; item consumed means the attempt settled, not that an
+ order or charge succeeded.
+ """
+
+ id: Optional[str] = None
+
+ approval_url: Optional[str] = None
+
+ expires_at: Optional[datetime] = None
+ """
+ When ready, the absolute deadline to submit the first native request; no later
+ than provider readiness expiry or 30 seconds after Kernel first observes
+ readiness. Polling never extends this deadline.
+ """
diff --git a/src/kernel/types/vaults/agentcard_prepared_processor.py b/src/kernel/types/vaults/agentcard_prepared_processor.py
new file mode 100644
index 00000000..41c3c7c2
--- /dev/null
+++ b/src/kernel/types/vaults/agentcard_prepared_processor.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__ = ["AgentcardPreparedProcessor"]
+
+AgentcardPreparedProcessor: TypeAlias = Literal["square", "braintree", "worldpay", "bambora", "mercado_pago", "adyen"]
diff --git a/src/kernel/types/vaults/authorize_vault_item_operation_request_param.py b/src/kernel/types/vaults/authorize_vault_item_operation_request_param.py
new file mode 100644
index 00000000..b2f9bee5
--- /dev/null
+++ b/src/kernel/types/vaults/authorize_vault_item_operation_request_param.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["AuthorizeVaultItemOperationRequestParam"]
+
+
+class AuthorizeVaultItemOperationRequestParam(TypedDict, total=False):
+ """Authorize a Link or Kernel card using its existing purchase specification.
+
+ Use only after explicit user approval and when the item advertises authorize; follow the advertised operation's description. For a Link card, pass browser_id and page_url together while the browser is on the final checkout page: Kernel inspects that checkout, chooses how the credential is delivered, and limits fill to that checkout's origin in any browser with this vault attached. Omitting both issues a virtual card that fills any page on the merchant origin. Once approval has been requested, browser_id and page_url are ignored and authorize only re-checks the request. Browser and checkout inspection errors occur before any provider request and can be retried once the browser is on the checkout page. Kernel cards do not accept browser_id or page_url.
+ """
+
+ type: Required[Literal["authorize"]]
+
+ browser_id: str
+ """Browser session ID, not a reusable browser name.
+
+ The browser must have this vault attached.
+ """
+
+ page_url: str
+ """Exact current top-level URL of the final checkout page open in browser_id."""
diff --git a/src/kernel/types/vaults/card_vault_item_spec.py b/src/kernel/types/vaults/card_vault_item_spec.py
new file mode 100644
index 00000000..749bf1d4
--- /dev/null
+++ b/src/kernel/types/vaults/card_vault_item_spec.py
@@ -0,0 +1,137 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .kernel_card_vault_item_spec import KernelCardVaultItemSpec
+
+__all__ = [
+ "CardVaultItemSpec",
+ "LinkCardVaultItemSpec",
+ "LinkCardVaultItemSpecLineItem",
+ "LinkCardVaultItemSpecLineItemTotal",
+ "LinkCardVaultItemSpecTotal",
+ "AgentCardCardVaultItemSpec",
+]
+
+
+class LinkCardVaultItemSpecLineItemTotal(BaseModel):
+ amount: int
+ """Total amount in minor currency units."""
+
+ display_text: str
+
+ type: str
+
+
+class LinkCardVaultItemSpecLineItem(BaseModel):
+ name: str
+
+ description: Optional[str] = None
+
+ image_url: Optional[str] = None
+
+ product_url: Optional[str] = None
+
+ quantity: Optional[int] = None
+
+ sku: Optional[str] = None
+
+ totals: Optional[List[LinkCardVaultItemSpecLineItemTotal]] = None
+
+ unit_amount: Optional[int] = None
+ """Unit amount in minor currency units."""
+
+ url: Optional[str] = None
+
+
+class LinkCardVaultItemSpecTotal(BaseModel):
+ amount: int
+ """Total amount in minor currency units."""
+
+ display_text: str
+
+ type: str
+
+
+class LinkCardVaultItemSpec(BaseModel):
+ """Live Link card request.
+
+ Test-mode card creation is not supported. The spec can be changed with PATCH until the card is authorized at a checkout. Authorize it with the browser at final checkout: Kernel inspects the checkout through WebMCP and internally chooses a merchant-bound Link Pay Token when supported or a virtual card otherwise. Callers never receive or choose how the credential is delivered.
+ """
+
+ amount: int
+ """Integer amount in minor currency units, at most 50000."""
+
+ context: str
+
+ currency: str
+
+ merchant_name: str
+
+ merchant_url: str
+
+ payment_method_id: str
+ """Payment-method ID returned by the referenced wallet's payment-method listing.
+
+ The provider decides whether the selected funding method can satisfy the card
+ request.
+ """
+
+ provider: Literal["link"]
+
+ wallet: str
+ """Connected Link wallet item key."""
+
+ line_items: Optional[List[LinkCardVaultItemSpecLineItem]] = None
+
+ metadata: Optional[Dict[str, str]] = None
+
+ totals: Optional[List[LinkCardVaultItemSpecTotal]] = None
+
+
+class AgentCardCardVaultItemSpec(BaseModel):
+ """AgentCard reusable live payment card.
+
+ Test-mode card creation is not supported. Each checkout creates an authorization for spec.merchant / spec.amount that the cardholder approves, unless AgentCard runs it under one of the cardholder's autopilot rules. The card stays ready after each authorization.
+ """
+
+ amount: int
+ """Integer amount in minor currency units."""
+
+ currency: str
+
+ merchant: str
+ """Merchant name shown on the cardholder's approval screen."""
+
+ provider: Literal["agentcard"]
+
+ wallet: str
+ """Wallet item key used to authorize checkouts."""
+
+ card_id: Optional[str] = None
+ """Opaque card ID returned by AgentCard for a card in the connected wallet.
+
+ Pass it through unchanged without assuming a prefix or format. Omitted, the
+ cardholder picks on the approval screen.
+ """
+
+ checkout_origin: Optional[str] = None
+ """
+ Origin of the top-level checkout page, such as https://shop.example.com: https,
+ a lowercase host, a port only when it is not 443, and no path. http is accepted
+ only for localhost test pages. Checkouts without a preparation send it to
+ AgentCard, which uses it to match the cardholder's autopilot rules; prepared
+ checkouts send the preparation's merchant_origin instead. Kernel sends the
+ declared value and does not compare it with the page the browser has open.
+ Omitted, those checkouts ask the cardholder to approve. Card updates replace the
+ whole spec, so an update that omits it removes it.
+ """
+
+
+CardVaultItemSpec: TypeAlias = Annotated[
+ Union[LinkCardVaultItemSpec, AgentCardCardVaultItemSpec, KernelCardVaultItemSpec],
+ PropertyInfo(discriminator="provider"),
+]
diff --git a/src/kernel/types/vaults/card_vault_item_spec_param.py b/src/kernel/types/vaults/card_vault_item_spec_param.py
new file mode 100644
index 00000000..c888247f
--- /dev/null
+++ b/src/kernel/types/vaults/card_vault_item_spec_param.py
@@ -0,0 +1,136 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Iterable
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+from .kernel_card_vault_item_spec_param import KernelCardVaultItemSpecParam
+
+__all__ = [
+ "CardVaultItemSpecParam",
+ "LinkCardVaultItemSpec",
+ "LinkCardVaultItemSpecLineItem",
+ "LinkCardVaultItemSpecLineItemTotal",
+ "LinkCardVaultItemSpecTotal",
+ "AgentCardCardVaultItemSpec",
+]
+
+
+class LinkCardVaultItemSpecLineItemTotal(TypedDict, total=False):
+ amount: Required[int]
+ """Total amount in minor currency units."""
+
+ display_text: Required[str]
+
+ type: Required[str]
+
+
+class LinkCardVaultItemSpecLineItem(TypedDict, total=False):
+ name: Required[str]
+
+ description: str
+
+ image_url: str
+
+ product_url: str
+
+ quantity: int
+
+ sku: str
+
+ totals: Iterable[LinkCardVaultItemSpecLineItemTotal]
+
+ unit_amount: int
+ """Unit amount in minor currency units."""
+
+ url: str
+
+
+class LinkCardVaultItemSpecTotal(TypedDict, total=False):
+ amount: Required[int]
+ """Total amount in minor currency units."""
+
+ display_text: Required[str]
+
+ type: Required[str]
+
+
+class LinkCardVaultItemSpec(TypedDict, total=False):
+ """Live Link card request.
+
+ Test-mode card creation is not supported. The spec can be changed with PATCH until the card is authorized at a checkout. Authorize it with the browser at final checkout: Kernel inspects the checkout through WebMCP and internally chooses a merchant-bound Link Pay Token when supported or a virtual card otherwise. Callers never receive or choose how the credential is delivered.
+ """
+
+ amount: Required[int]
+ """Integer amount in minor currency units, at most 50000."""
+
+ context: Required[str]
+
+ currency: Required[str]
+
+ merchant_name: Required[str]
+
+ merchant_url: Required[str]
+
+ payment_method_id: Required[str]
+ """Payment-method ID returned by the referenced wallet's payment-method listing.
+
+ The provider decides whether the selected funding method can satisfy the card
+ request.
+ """
+
+ provider: Required[Literal["link"]]
+
+ wallet: Required[str]
+ """Connected Link wallet item key."""
+
+ line_items: Iterable[LinkCardVaultItemSpecLineItem]
+
+ metadata: Dict[str, str]
+
+ totals: Iterable[LinkCardVaultItemSpecTotal]
+
+
+class AgentCardCardVaultItemSpec(TypedDict, total=False):
+ """AgentCard reusable live payment card.
+
+ Test-mode card creation is not supported. Each checkout creates an authorization for spec.merchant / spec.amount that the cardholder approves, unless AgentCard runs it under one of the cardholder's autopilot rules. The card stays ready after each authorization.
+ """
+
+ amount: Required[int]
+ """Integer amount in minor currency units."""
+
+ currency: Required[str]
+
+ merchant: Required[str]
+ """Merchant name shown on the cardholder's approval screen."""
+
+ provider: Required[Literal["agentcard"]]
+
+ wallet: Required[str]
+ """Wallet item key used to authorize checkouts."""
+
+ card_id: str
+ """Opaque card ID returned by AgentCard for a card in the connected wallet.
+
+ Pass it through unchanged without assuming a prefix or format. Omitted, the
+ cardholder picks on the approval screen.
+ """
+
+ checkout_origin: str
+ """
+ Origin of the top-level checkout page, such as https://shop.example.com: https,
+ a lowercase host, a port only when it is not 443, and no path. http is accepted
+ only for localhost test pages. Checkouts without a preparation send it to
+ AgentCard, which uses it to match the cardholder's autopilot rules; prepared
+ checkouts send the preparation's merchant_origin instead. Kernel sends the
+ declared value and does not compare it with the page the browser has open.
+ Omitted, those checkouts ask the cardholder to approve. Card updates replace the
+ whole spec, so an update that omits it removes it.
+ """
+
+
+CardVaultItemSpecParam: TypeAlias = Union[
+ LinkCardVaultItemSpec, AgentCardCardVaultItemSpec, KernelCardVaultItemSpecParam
+]
diff --git a/src/kernel/types/vaults/card_vault_item_state.py b/src/kernel/types/vaults/card_vault_item_state.py
new file mode 100644
index 00000000..37dbdecd
--- /dev/null
+++ b/src/kernel/types/vaults/card_vault_item_state.py
@@ -0,0 +1,138 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .kernel_card_state import KernelCardState
+from .vault_card_aliases import VaultCardAliases
+from .agentcard_checkout_preparation import AgentcardCheckoutPreparation
+from .agentcard_checkout_authorization import AgentcardCheckoutAuthorization
+
+__all__ = ["CardVaultItemState", "LinkCardState", "LinkCardStateMasks", "AgentCardCardState", "AgentCardCardStateMasks"]
+
+
+class LinkCardStateMasks(BaseModel):
+ brand: Optional[str] = None
+
+ last4: Optional[str] = None
+
+ token_last4: Optional[str] = None
+ """Last four digits of the network token presented to the merchant."""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, str] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> str: ...
+ else:
+ __pydantic_extra__: Dict[str, str]
+
+
+class LinkCardState(BaseModel):
+ """Issued Link cards retain encrypted card material for the fill operation.
+
+ Link cards do not expose aliases or support egress substitution.
+ """
+
+ provider: Literal["link"]
+
+ status: Literal[
+ "requested", "pending_authorization", "ready", "consumed", "expired", "declined", "recovery_required"
+ ]
+ """
+ recovery_required means an original provider operation has an unresolved outcome
+ and the card cannot be used. Reads keep observing a known provider request and
+ may clear the recovery. To continue, delete this card and create a new one;
+ deleting cancels the original request when its ID is known and keeps the
+ recovery evidence. The user may see an approval request for the original card
+ and should approve only the new one.
+ """
+
+ domains: Optional[List[str]] = None
+
+ masks: Optional[LinkCardStateMasks] = None
+
+ status_reason: Optional[str] = None
+
+
+class AgentCardCardStateMasks(BaseModel):
+ brand: Optional[str] = None
+
+ last4: Optional[str] = None
+
+ token_last4: Optional[str] = None
+ """Last four digits of the network token presented to the merchant."""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, str] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> str: ...
+ else:
+ __pydantic_extra__: Dict[str, str]
+
+
+class AgentCardCardState(BaseModel):
+ provider: Literal["agentcard"]
+
+ status: Literal[
+ "requested",
+ "ready",
+ "preparing",
+ "ready_to_submit",
+ "pending_approval",
+ "consumed",
+ "stopped",
+ "outcome_unknown",
+ "degraded",
+ "recovery_required",
+ ]
+ """ready_to_submit is device readiness for at most 30 seconds.
+
+ consumed means the prepared attempt has settled, not that an order succeeded.
+ stopped cannot be reused. outcome_unknown requires merchant reconciliation and
+ blocks new requests. recovery_required means the original checkout outcome is
+ unresolved. Automatic reuse is blocked. Known authorization IDs must be
+ reconciled through provider observations or support. When no authorization ID
+ was returned, an explicitly confirmed item deletion may abandon the unresolved
+ attempt so the caller can create a replacement; deletion does not prove that the
+ original attempt failed. It does not mean declined or expired.
+ """
+
+ aliases: Optional[VaultCardAliases] = None
+
+ authorization: Optional[AgentcardCheckoutAuthorization] = None
+ """The in-flight or most recent checkout authorization.
+
+ Present while a checkout is pending approval and after it settles.
+ """
+
+ masks: Optional[AgentCardCardStateMasks] = None
+
+ preparation: Optional[AgentcardCheckoutPreparation] = None
+ """One-use processor-bound checkout preparation.
+
+ Keep the approval page open through device handoff, including Adyen encryption.
+ The amount is declared by the caller and does not constrain the merchant's
+ eventual charge. Adyen device approval and browser Authorised responses are not
+ capture or fulfillment evidence.
+ """
+
+ status_reason: Optional[str] = None
+
+
+CardVaultItemState: TypeAlias = Annotated[
+ Union[LinkCardState, AgentCardCardState, KernelCardState], PropertyInfo(discriminator="provider")
+]
diff --git a/src/kernel/types/vaults/collect_vault_item_operation_request_param.py b/src/kernel/types/vaults/collect_vault_item_operation_request_param.py
new file mode 100644
index 00000000..4038b90e
--- /dev/null
+++ b/src/kernel/types/vaults/collect_vault_item_operation_request_param.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["CollectVaultItemOperationRequestParam"]
+
+
+class CollectVaultItemOperationRequestParam(TypedDict, total=False):
+ """Return the credential item with its collection action.
+
+ Supported for ready
+ and pending_collection credential items. Always render the same form from
+ every form-supported field; totp and passkey fields have no form input and are omitted.
+ No caller-selected field subsets or form overrides are accepted.
+ Reuse an active Kernel-hosted session or renew an expired session atomically.
+ Customer-hosted forms use their own backend and ordinary item GET/PATCH. Opening
+ the form does not clear values or change readiness or item version.
+ To observe edits on a ready item, record its version and poll GET without
+ wait until the version changes, then reconcile the returned state. Version
+ changes may also come from PATCH; they do not identify a particular form
+ submission. Customer-hosted apps use their own submission callback, including
+ for unchanged forms. The wait parameter waits for readiness, not edits.
+ """
+
+ type: Required[Literal["collect"]]
diff --git a/src/kernel/types/vaults/confirm_transaction_vault_item_operation_request_param.py b/src/kernel/types/vaults/confirm_transaction_vault_item_operation_request_param.py
new file mode 100644
index 00000000..3b0e0485
--- /dev/null
+++ b/src/kernel/types/vaults/confirm_transaction_vault_item_operation_request_param.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import datetime
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["ConfirmTransactionVaultItemOperationRequestParam"]
+
+
+class ConfirmTransactionVaultItemOperationRequestParam(TypedDict, total=False):
+ """Report a real merchant transaction outcome for an issued Kernel Visa credential.
+
+ Never infer success from filling checkout or receiving a credential. Supply the observed status, transaction type, actual amount, currency and timestamp. Unknown outcomes require manual reconciliation, not retry. Each purchase accepts one report.
+ """
+
+ amount: Required[int]
+ """Actual transaction amount in minor units, no greater than the approved limit."""
+
+ currency: Required[str]
+ """Must match the approved purchase currency."""
+
+ occurred_at: Required[Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]]
+ """Time the merchant outcome was observed."""
+
+ status: Required[Literal["APPROVED", "DECLINED", "PENDING", "ERROR", "CANCELLED"]]
+
+ transaction_type: Required[
+ Literal["PURCHASE", "AUTHORIZATION", "CAPTURE", "REFUND", "REVERSAL", "VERIFICATION", "CHARGEBACK", "FRAUD"]
+ ]
+
+ type: Required[Literal["confirm_transaction"]]
diff --git a/src/kernel/types/vaults/credential_account_vault_item.py b/src/kernel/types/vaults/credential_account_vault_item.py
new file mode 100644
index 00000000..a049c193
--- /dev/null
+++ b/src/kernel/types/vaults/credential_account_vault_item.py
@@ -0,0 +1,76 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .one_password_oauth_action import OnePasswordOAuthAction
+from .one_password_credential_account_spec import OnePasswordCredentialAccountSpec
+from .one_password_credential_account_state import OnePasswordCredentialAccountState
+
+__all__ = ["CredentialAccountVaultItem", "AvailableExpansion", "AvailableOperation"]
+
+
+class AvailableExpansion(BaseModel):
+ """
+ Live data that can currently be requested by passing its type to the item GET expand parameter.
+ """
+
+ description: str
+
+ type: Literal["payment_methods"]
+
+
+class AvailableOperation(BaseModel):
+ """An operation that is currently valid for this item.
+
+ Read the description before invoking it through the item operations endpoint.
+ """
+
+ description: str
+
+ type: Literal[
+ "authorize",
+ "restart_verification",
+ "confirm_transaction",
+ "collect",
+ "prepare_checkout",
+ "fill",
+ "1pw_create_access_request",
+ "1pw_access_request_status",
+ "1pw_fill",
+ "1pw_recover",
+ "1pw_update_access_token",
+ "webmcp_invoke",
+ ]
+
+
+class CredentialAccountVaultItem(BaseModel):
+ id: str
+
+ available_expansions: List[AvailableExpansion]
+
+ available_operations: List[AvailableOperation]
+ """Advertises 1pw_recover when Kernel can recover a failed account link.
+
+ Recovery is unavailable while authorization is pending or after the connection
+ has already been reset.
+ """
+
+ created_at: datetime
+
+ key: str
+ """Immutable item key assigned when the item is created."""
+
+ spec: OnePasswordCredentialAccountSpec
+
+ state: OnePasswordCredentialAccountState
+
+ type: Literal["credential_account"]
+
+ updated_at: datetime
+
+ action: Optional[OnePasswordOAuthAction] = None
+
+ expires_at: Optional[datetime] = None
diff --git a/src/kernel/types/vaults/credential_account_vault_item_request_param.py b/src/kernel/types/vaults/credential_account_vault_item_request_param.py
new file mode 100644
index 00000000..187b25f2
--- /dev/null
+++ b/src/kernel/types/vaults/credential_account_vault_item_request_param.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, Required, TypedDict
+
+from .one_password_credential_account_spec_param import OnePasswordCredentialAccountSpecParam
+
+__all__ = ["CredentialAccountVaultItemRequestParam"]
+
+
+class CredentialAccountVaultItemRequestParam(TypedDict, total=False):
+ spec: Required[OnePasswordCredentialAccountSpecParam]
+
+ type: Required[Literal["credential_account"]]
diff --git a/src/kernel/types/vaults/credential_collection_action.py b/src/kernel/types/vaults/credential_collection_action.py
new file mode 100644
index 00000000..2f1cd48c
--- /dev/null
+++ b/src/kernel/types/vaults/credential_collection_action.py
@@ -0,0 +1,50 @@
+# 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__ = ["CredentialCollectionAction"]
+
+
+class CredentialCollectionAction(BaseModel):
+ """
+ One schema-derived form for the item, available in ready or pending_collection
+ state. Render every form-supported field as editable; omit totp and passkey
+ fields and preserve their stored values. Prefill non-sensitive values,
+ and allow existing sensitive values to be preserved or replaced without ever
+ revealing them. No field subsets or per-request form configuration exist.
+ Validate required fields against the resulting values, including preserved
+ secrets. Submit changed values only, using the version used to render the form.
+ Scoped hosted submission rejects totp and passkey edits; those writes require the ordinary
+ authenticated item API. Customer forms likewise omit them from their payloads.
+ Save edits atomically. A successful hosted submission increments the version,
+ marks ready, and consumes the session; an empty edit may complete collection
+ while preserving values. A customer form uses PATCH for changed values and
+ does not send an empty PATCH when nothing changed.
+ Kernel-hosted bearer sessions require no Kernel account and are bound to the
+ item version. Expired, superseded, consumed, or deleted-item sessions cannot
+ submit. Authenticated item GET renews expired active sessions for ready or pending items; pending
+ items always receive an action. A ready item with no active session omits the
+ action until collect is invoked. Concurrent renewals return the same link.
+ Renewal changes neither values nor item version. An expired link cannot renew
+ itself. The hosted form handles its collection protocol; callers only open
+ the returned URL and do not extract or submit its token through the public API.
+ For customer-hosted forms, use @onkernel/vault-react and an authenticated
+ customer backend calling the ordinary item GET/PATCH API. Kernel does not
+ store customer collection URLs or authenticate the customer's end users.
+ Treat URLs and submitted values as secrets and exclude them from logs,
+ traces, and errors.
+ """
+
+ expires_at: datetime
+ """Expiry of the Kernel-hosted collection link (30 minutes after issuance)."""
+
+ name: Literal["collect"]
+
+ url: str
+ """Time-scoped hosted form URL (vault.kernel.sh in production).
+
+ Open this URL as returned; treat it as a secret.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_field_definition.py b/src/kernel/types/vaults/credential_vault_field_definition.py
new file mode 100644
index 00000000..c8317687
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_field_definition.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .credential_vault_field_type import CredentialVaultFieldType
+
+__all__ = ["CredentialVaultFieldDefinition"]
+
+
+class CredentialVaultFieldDefinition(BaseModel):
+ name: str
+ """Stable field name used to key values, updates, and browser fills."""
+
+ required: bool
+ """Whether a nonempty value is required for readiness and form submission."""
+
+ sensitive: bool
+ """Whether the value is omitted from every item response.
+
+ Reserve true for secrets such as passwords, API tokens, and TOTP seeds. Ordinary
+ usernames and email addresses should be false so the form can display and
+ prefill them.
+ """
+
+ type: CredentialVaultFieldType
+ """
+ Text, email, and password have form inputs; totp and passkey do not and are
+ omitted from both Kernel-hosted and customer React forms. Password, totp, and
+ passkey must be sensitive. A totp value is an RFC 4648 Base32 generator seed
+ (case-insensitive, optional trailing padding), not an otpauth URI or current
+ code. Reject invalid or empty decoded seeds. Browser fill generates an RFC 6238
+ code at execution time using HMAC-SHA1, 6 digits, and a 30-second period.
+ Preserve leading zeros; never fill the seed. Custom algorithms, digits, periods,
+ and form enrollment are unsupported. A passkey value is a JSON-encoded FIDO
+ Credential Exchange Format passkey object with unpadded base64url
+ `credentialId`, `userHandle` (at most 64 bytes), and `key` (a PKCS#8 ECDSA P-256
+ private key), a lowercase `rpId` domain, and optional `username` and
+ `userDisplayName`. Other CXF members are ignored. Passkeys are never filled into
+ pages.
+ """
+
+ label: Optional[str] = None
+ """Optional human-readable display label.
+
+ It is returned as non-secret metadata and never affects value keys, updates, or
+ browser fills. Use single-line, trimmed display text without control or
+ formatting characters. The server enforces a 128-byte UTF-8 limit.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_field_definition_input_param.py b/src/kernel/types/vaults/credential_vault_field_definition_input_param.py
new file mode 100644
index 00000000..f97cf911
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_field_definition_input_param.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .credential_vault_field_type import CredentialVaultFieldType
+
+__all__ = ["CredentialVaultFieldDefinitionInputParam"]
+
+
+class CredentialVaultFieldDefinitionInputParam(TypedDict, total=False):
+ """A field definition without a value.
+
+ Field updates add fields with this shape and set their values in `fields`.
+ """
+
+ name: Required[str]
+ """Unique stable field name used to key values, updates, and browser fills."""
+
+ type: Required[CredentialVaultFieldType]
+ """
+ Text, email, and password have form inputs; totp and passkey do not and are
+ omitted from both Kernel-hosted and customer React forms. Password, totp, and
+ passkey must be sensitive. A totp value is an RFC 4648 Base32 generator seed
+ (case-insensitive, optional trailing padding), not an otpauth URI or current
+ code. Reject invalid or empty decoded seeds. Browser fill generates an RFC 6238
+ code at execution time using HMAC-SHA1, 6 digits, and a 30-second period.
+ Preserve leading zeros; never fill the seed. Custom algorithms, digits, periods,
+ and form enrollment are unsupported. A passkey value is a JSON-encoded FIDO
+ Credential Exchange Format passkey object with unpadded base64url
+ `credentialId`, `userHandle` (at most 64 bytes), and `key` (a PKCS#8 ECDSA P-256
+ private key), a lowercase `rpId` domain, and optional `username` and
+ `userDisplayName`. Other CXF members are ignored. Passkeys are never filled into
+ pages.
+ """
+
+ label: str
+ """Optional human-readable display label.
+
+ It is returned as non-secret metadata and never affects value keys, updates, or
+ browser fills. Use single-line, trimmed display text without control or
+ formatting characters. The server enforces a 128-byte UTF-8 limit.
+ """
+
+ required: bool
+
+ sensitive: bool
+ """
+ Set false explicitly for ordinary usernames, email addresses, and other
+ non-secret identifiers. Reserve true for secrets such as passwords, API tokens,
+ and TOTP seeds. Password, totp, and passkey fields must be true. Omission
+ defaults to true for safety; do not rely on that default for every field. False
+ permits API reads and form prefilling.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_field_input_param.py b/src/kernel/types/vaults/credential_vault_field_input_param.py
new file mode 100644
index 00000000..5e26bb8d
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_field_input_param.py
@@ -0,0 +1,75 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .credential_vault_field_type import CredentialVaultFieldType
+
+__all__ = ["CredentialVaultFieldInputParam"]
+
+
+class CredentialVaultFieldInputParam(TypedDict, total=False):
+ name: Required[str]
+ """Unique stable field name used to key values, updates, and browser fills."""
+
+ type: Required[CredentialVaultFieldType]
+ """
+ Text, email, and password have form inputs; totp and passkey do not and are
+ omitted from both Kernel-hosted and customer React forms. Password, totp, and
+ passkey must be sensitive. A totp value is an RFC 4648 Base32 generator seed
+ (case-insensitive, optional trailing padding), not an otpauth URI or current
+ code. Reject invalid or empty decoded seeds. Browser fill generates an RFC 6238
+ code at execution time using HMAC-SHA1, 6 digits, and a 30-second period.
+ Preserve leading zeros; never fill the seed. Custom algorithms, digits, periods,
+ and form enrollment are unsupported. A passkey value is a JSON-encoded FIDO
+ Credential Exchange Format passkey object with unpadded base64url
+ `credentialId`, `userHandle` (at most 64 bytes), and `key` (a PKCS#8 ECDSA P-256
+ private key), a lowercase `rpId` domain, and optional `username` and
+ `userDisplayName`. Other CXF members are ignored. Passkeys are never filled into
+ pages.
+ """
+
+ encrypted_value: str
+ """Alternative to value for custom credential collection web apps.
+
+ Use this only if you run your own credential collection web app and want values
+ encrypted in the browser, sent to your backend still encrypted, and forwarded to
+ Kernel's API still encrypted. In every other case, including server-side code
+ that already holds the plaintext, use value. The field's value encrypted
+ client-side as a compact JWE with alg ECDH-ES and enc A256GCM to the key from
+ GET /vaults/{id_or_name}/encryption_key, with that key's kid in the protected
+ header. Compression is not supported. The decrypted value follows the same rules
+ as value, including that an empty string clears the field on update. A kid that
+ is not this vault's key returns 400 encryption_key_mismatch; fetch the key again
+ and re-encrypt. Other malformed or undecryptable values return 400
+ invalid_request. The whole request body is limited to 128 KiB.
+ """
+
+ label: str
+ """Optional human-readable display label.
+
+ It is returned as non-secret metadata and never affects value keys, updates, or
+ browser fills. Use single-line, trimmed display text without control or
+ formatting characters. The server enforces a 128-byte UTF-8 limit.
+ """
+
+ required: bool
+
+ sensitive: bool
+ """
+ Set false explicitly for ordinary usernames, email addresses, and other
+ non-secret identifiers. Reserve true for secrets such as passwords, API tokens,
+ and TOTP seeds. Password, totp, and passkey fields must be true. Omission
+ defaults to true for safety; do not rely on that default for every field. False
+ permits API reads and form prefilling.
+ """
+
+ value: str
+ """
+ Optional initial value satisfying the declared type, at most 16 KiB in UTF-8
+ bytes. Omit to leave unset; null and empty strings are rejected on creation.
+ Sensitive values are encrypted and never copied into the returned spec. Use this
+ unless your own credential collection web app encrypts values in the browser;
+ mutually exclusive with encrypted_value.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_field_state.py b/src/kernel/types/vaults/credential_vault_field_state.py
new file mode 100644
index 00000000..1c2935cd
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_field_state.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["CredentialVaultFieldState"]
+
+
+class CredentialVaultFieldState(BaseModel):
+ has_value: bool
+
+ value: Optional[str] = None
+ """Present exactly when has_value is true and the field is not sensitive.
+
+ Reflects the latest developer or human edit. For totp, has_value indicates a
+ stored seed; neither the seed nor a generated code is returned.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_field_type.py b/src/kernel/types/vaults/credential_vault_field_type.py
new file mode 100644
index 00000000..67e5368e
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_field_type.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__ = ["CredentialVaultFieldType"]
+
+CredentialVaultFieldType: TypeAlias = Literal["text", "email", "password", "totp", "passkey"]
diff --git a/src/kernel/types/vaults/credential_vault_field_update_param.py b/src/kernel/types/vaults/credential_vault_field_update_param.py
new file mode 100644
index 00000000..b33079bf
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_field_update_param.py
@@ -0,0 +1,42 @@
+# 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__ = ["CredentialVaultFieldUpdateParam"]
+
+
+class CredentialVaultFieldUpdateParam(TypedDict, total=False):
+ """Set exactly one of value or encrypted_value.
+
+ Use value unless the update comes from your own credential collection web app that encrypts values in the browser; see encrypted_value.
+ """
+
+ encrypted_value: str
+ """Alternative to value for custom credential collection web apps.
+
+ Use this only if you run your own credential collection web app and want values
+ encrypted in the browser, sent to your backend still encrypted, and forwarded to
+ Kernel's API still encrypted. In every other case, including server-side code
+ that already holds the plaintext, use value. The field's value encrypted
+ client-side as a compact JWE with alg ECDH-ES and enc A256GCM to the key from
+ GET /vaults/{id_or_name}/encryption_key, with that key's kid in the protected
+ header. Compression is not supported. The decrypted value follows the same rules
+ as value, including that an empty string clears the field on update. A kid that
+ is not this vault's key returns 400 encryption_key_mismatch; fetch the key again
+ and re-encrypt. Other malformed or undecryptable values return 400
+ invalid_request. The whole request body is limited to 128 KiB.
+ """
+
+ value: Optional[str]
+ """
+ Replacement value (at most 16 KiB in UTF-8 bytes), or null or an empty string to
+ immediately clear the stored value. Clearing a required form-supported field
+ reopens collection; clearing an optional field does not prevent readiness.
+ Values must satisfy the declared field type. For totp, value is the generator
+ seed, never a current code. For passkey, value is the JSON-encoded CXF passkey
+ object. Clearing a required totp or passkey field returns 400 because it cannot
+ be collected in a form.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_item.py b/src/kernel/types/vaults/credential_vault_item.py
new file mode 100644
index 00000000..91f6d8e1
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_item.py
@@ -0,0 +1,144 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .credential_vault_item_spec import CredentialVaultItemSpec
+from .credential_vault_item_state import CredentialVaultItemState
+from .credential_collection_action import CredentialCollectionAction
+
+__all__ = [
+ "CredentialVaultItem",
+ "AvailableExpansion",
+ "AvailableOperation",
+ "Action",
+ "ActionOnePasswordAccessApprovalAction",
+]
+
+
+class AvailableExpansion(BaseModel):
+ """
+ Live data that can currently be requested by passing its type to the item GET expand parameter.
+ """
+
+ description: str
+
+ type: Literal["payment_methods"]
+
+
+class AvailableOperation(BaseModel):
+ """An operation that is currently valid for this item.
+
+ Read the description before invoking it through the item operations endpoint.
+ """
+
+ description: str
+
+ type: Literal[
+ "authorize",
+ "restart_verification",
+ "confirm_transaction",
+ "collect",
+ "prepare_checkout",
+ "fill",
+ "1pw_create_access_request",
+ "1pw_access_request_status",
+ "1pw_fill",
+ "1pw_recover",
+ "1pw_update_access_token",
+ "webmcp_invoke",
+ ]
+
+
+class ActionOnePasswordAccessApprovalAction(BaseModel):
+ instructions: str
+ """
+ Steps for the agent to hand approval to the human and poll the resulting
+ decision.
+ """
+
+ name: Literal["1password_access_approval"]
+
+ url: str
+ """Native 1Password approval link.
+
+ Present it to the account owner without modifying it; it does not grant access
+ until they approve in their app.
+ """
+
+
+Action: TypeAlias = Annotated[
+ Union[CredentialCollectionAction, ActionOnePasswordAccessApprovalAction], PropertyInfo(discriminator="name")
+]
+
+
+class CredentialVaultItem(BaseModel):
+ id: str
+
+ available_expansions: List[AvailableExpansion]
+
+ available_operations: List[AvailableOperation]
+ """Kernel credentials advertise collect and fill when eligible.
+
+ 1Password credentials advertise 1pw_create_access_request until a request is
+ made, 1pw_access_request_status while its approval is pending, and 1pw_fill
+ after access is granted. Managed auth credentials advertise fill while ready and
+ nothing otherwise.
+ """
+
+ created_at: datetime
+
+ key: str
+ """Immutable item key assigned when the item is created."""
+
+ spec: CredentialVaultItemSpec
+ """
+ Stored-token credentials omit account and never return access_token or
+ integration_key.
+ """
+
+ state: CredentialVaultItemState
+
+ type: Literal["credential"]
+
+ updated_at: datetime
+
+ version: int
+ """
+ Starts at 1 and increments on PATCH and successful hosted submission, but not
+ collection-link renewal. Managed auth credentials stay at 1; changes to the
+ underlying credential are read at fill time and do not change the version.
+ """
+
+ action: Optional[Action] = None
+ """
+ One schema-derived form for the item, available in ready or pending_collection
+ state. Render every form-supported field as editable; omit totp and passkey
+ fields and preserve their stored values. Prefill non-sensitive values, and allow
+ existing sensitive values to be preserved or replaced without ever revealing
+ them. No field subsets or per-request form configuration exist. Validate
+ required fields against the resulting values, including preserved secrets.
+ Submit changed values only, using the version used to render the form. Scoped
+ hosted submission rejects totp and passkey edits; those writes require the
+ ordinary authenticated item API. Customer forms likewise omit them from their
+ payloads. Save edits atomically. A successful hosted submission increments the
+ version, marks ready, and consumes the session; an empty edit may complete
+ collection while preserving values. A customer form uses PATCH for changed
+ values and does not send an empty PATCH when nothing changed. Kernel-hosted
+ bearer sessions require no Kernel account and are bound to the item version.
+ Expired, superseded, consumed, or deleted-item sessions cannot submit.
+ Authenticated item GET renews expired active sessions for ready or pending
+ items; pending items always receive an action. A ready item with no active
+ session omits the action until collect is invoked. Concurrent renewals return
+ the same link. Renewal changes neither values nor item version. An expired link
+ cannot renew itself. The hosted form handles its collection protocol; callers
+ only open the returned URL and do not extract or submit its token through the
+ public API. For customer-hosted forms, use @onkernel/vault-react and an
+ authenticated customer backend calling the ordinary item GET/PATCH API. Kernel
+ does not store customer collection URLs or authenticate the customer's end
+ users. Treat URLs and submitted values as secrets and exclude them from logs,
+ traces, and errors.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_item_request_param.py b/src/kernel/types/vaults/credential_vault_item_request_param.py
new file mode 100644
index 00000000..4034d9c5
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_item_request_param.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+from .credential_vault_item_spec_input_param import CredentialVaultItemSpecInputParam
+
+__all__ = ["CredentialVaultItemRequestParam"]
+
+
+class CredentialVaultItemRequestParam(TypedDict, total=False):
+ """
+ Ask the end-user whether to link their site credential through 1Password.
+ If they choose 1Password, connect their account and request access to a login
+ in their own non-shared vault; passkeys are not supported. If they decline
+ or that path fails, collect a Kernel-hosted credential item instead. Never
+ automatically retry an uncertain 1Password request. After an uncertain fill,
+ inspect the page to see the result of the fill.
+ Do not use credential items for credit card data. Use wallet and card item types instead.
+ Kernel credentials declare fields and may enter pending_collection.
+ 1Password credentials either reference a connected credential_account or
+ store a supplied access token and integration key encrypted on the item.
+ They store no login values or selectors.
+ Managed auth credentials reference a managed auth connection in the same
+ project that already has a saved credential, and read it at fill time;
+ they store no values.
+ Repeating the original creation request returns the current item without
+ overwriting later state. A different request at the same key returns 409.
+ """
+
+ spec: Required[CredentialVaultItemSpecInputParam]
+ """Credential fields are for login and other non-payment credentials.
+
+ Do not store, collect, or fill credit card data in credential items. Use wallet
+ and card item types for credit cards and payment checkout instead. Field order
+ is preserved in the user-facing collection form, so list fields in the same
+ top-to-bottom order as the website.
+ """
+
+ type: Required[Literal["credential"]]
diff --git a/src/kernel/types/vaults/credential_vault_item_spec.py b/src/kernel/types/vaults/credential_vault_item_spec.py
new file mode 100644
index 00000000..14d9503b
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_item_spec.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from .kernel_credential_vault_item_spec import KernelCredentialVaultItemSpec
+from .managed_auth_credential_vault_item_spec import ManagedAuthCredentialVaultItemSpec
+from .one_password_credential_vault_item_spec import OnePasswordCredentialVaultItemSpec
+
+__all__ = ["CredentialVaultItemSpec"]
+
+CredentialVaultItemSpec: TypeAlias = Annotated[
+ Union[KernelCredentialVaultItemSpec, OnePasswordCredentialVaultItemSpec, ManagedAuthCredentialVaultItemSpec],
+ PropertyInfo(discriminator="provider"),
+]
diff --git a/src/kernel/types/vaults/credential_vault_item_spec_input_param.py b/src/kernel/types/vaults/credential_vault_item_spec_input_param.py
new file mode 100644
index 00000000..b285d31d
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_item_spec_input_param.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .kernel_credential_vault_item_spec_input_param import KernelCredentialVaultItemSpecInputParam
+from .managed_auth_credential_vault_item_spec_input_param import ManagedAuthCredentialVaultItemSpecInputParam
+from .one_password_credential_vault_item_spec_input_param import OnePasswordCredentialVaultItemSpecInputParam
+
+__all__ = ["CredentialVaultItemSpecInputParam"]
+
+CredentialVaultItemSpecInputParam: TypeAlias = Union[
+ KernelCredentialVaultItemSpecInputParam,
+ OnePasswordCredentialVaultItemSpecInputParam,
+ ManagedAuthCredentialVaultItemSpecInputParam,
+]
diff --git a/src/kernel/types/vaults/credential_vault_item_spec_update_param.py b/src/kernel/types/vaults/credential_vault_item_spec_update_param.py
new file mode 100644
index 00000000..06dde966
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_item_spec_update_param.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Iterable
+from typing_extensions import TypedDict
+
+from ..._types import SequenceNotStr
+from .credential_vault_field_update_param import CredentialVaultFieldUpdateParam
+from .credential_vault_field_definition_input_param import CredentialVaultFieldDefinitionInputParam
+
+__all__ = ["CredentialVaultItemSpecUpdateParam"]
+
+
+class CredentialVaultItemSpecUpdateParam(TypedDict, total=False):
+ add_fields: Iterable[CredentialVaultFieldDefinitionInputParam]
+ """Field definitions to append, with the same defaults as creation.
+
+ Set their values in `fields` in the same update. Names must be unique after
+ removals; at most 32 fields may result. A required totp or passkey field needs a
+ value in `fields`.
+ """
+
+ description: str
+ """
+ Recognizable site or service name used as the form title, without suffixes such
+ as sign-in credentials. An empty string clears it. Display text only, not an
+ enforced destination policy. The server also enforces a 16 KiB UTF-8 byte limit.
+ """
+
+ fields: Dict[str, CredentialVaultFieldUpdateParam]
+
+ remove_fields: SequenceNotStr[str]
+ """Names of fields to delete with their values.
+
+ Unknown names return 400. At least one field must remain.
+ """
diff --git a/src/kernel/types/vaults/credential_vault_item_state.py b/src/kernel/types/vaults/credential_vault_item_state.py
new file mode 100644
index 00000000..d8564703
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_item_state.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from .kernel_credential_vault_item_state import KernelCredentialVaultItemState
+from .managed_auth_credential_vault_item_state import ManagedAuthCredentialVaultItemState
+from .one_password_credential_vault_item_state import OnePasswordCredentialVaultItemState
+
+__all__ = ["CredentialVaultItemState"]
+
+CredentialVaultItemState: TypeAlias = Annotated[
+ Union[KernelCredentialVaultItemState, OnePasswordCredentialVaultItemState, ManagedAuthCredentialVaultItemState],
+ PropertyInfo(discriminator="provider"),
+]
diff --git a/src/kernel/types/vaults/credential_vault_item_update_request_param.py b/src/kernel/types/vaults/credential_vault_item_update_request_param.py
new file mode 100644
index 00000000..5c45f9df
--- /dev/null
+++ b/src/kernel/types/vaults/credential_vault_item_update_request_param.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+from .credential_vault_item_spec_update_param import CredentialVaultItemSpecUpdateParam
+
+__all__ = ["CredentialVaultItemUpdateRequestParam"]
+
+
+class CredentialVaultItemUpdateRequestParam(TypedDict, total=False):
+ """
+ Atomically update description, field definitions, and selected values.
+ Omitted properties are preserved. `remove_fields` deletes definitions and
+ their values, then `add_fields` appends definitions, then `fields` sets
+ values, including for added fields. An existing field's definition cannot
+ be edited in place; to change it, remove the field and add it again, in
+ the same update or a later one. The old value is deleted with it.
+ Unknown field names return 400; stale versions or mismatched item types
+ return 409 without changing the item. A successful update increments version
+ and invalidates outstanding Kernel-hosted collection sessions. If required
+ values remain missing, return pending_collection and a fresh collection
+ action. Otherwise return ready without an action; collect can open the form
+ again without clearing values. Customer URLs have no Kernel-managed expiry.
+ 1Password and managed auth credentials return 409.
+ """
+
+ spec: Required[CredentialVaultItemSpecUpdateParam]
+
+ type: Required[Literal["credential"]]
+
+ version: Required[int]
+ """Expected current item version from the latest read."""
+
+ expected_item_id: str
+ """Optional immutable item ID precondition.
+
+ Returns 409 if the key now identifies a different item. Accepted writes target
+ this immutable ID, preventing replacement-key races. Supply this when submitting
+ a form bound to a previously read item.
+ """
diff --git a/src/kernel/types/vaults/fill_vault_item_operation_request_param.py b/src/kernel/types/vaults/fill_vault_item_operation_request_param.py
new file mode 100644
index 00000000..a86966d1
--- /dev/null
+++ b/src/kernel/types/vaults/fill_vault_item_operation_request_param.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+from .vault_fill_field_param import VaultFillFieldParam
+
+__all__ = ["FillVaultItemOperationRequestParam"]
+
+
+class FillVaultItemOperationRequestParam(TypedDict, total=False):
+ """
+ Use a vault item in a browser session, for example to fill a login or
+ payment form. Only invoke when the item advertises `fill`; the advertised
+ operation describes the inputs that item needs. Requires `browser_id` and
+ usually `page_url`; some items also require `fields`. Browser and vault must
+ belong to the same project. Kernel checks access and allowed destinations
+ before filling; providing a page URL does not authorize a destination.
+
+ Fill never submits forms or clicks buttons, though input and change events
+ may trigger site behavior. It is not atomic: completed steps are not rolled
+ back. Fill does not consume the item, so a failed or indeterminate fill is
+ safe to retry. A retry right after an indeterminate fill may wait up to 15
+ seconds for the earlier attempt's browser lock to expire.
+
+ Secret values are never returned or included in operation logs, traces,
+ audit events, or error details. This does not prevent an agent with
+ unrestricted browser access from reading values from the page or other
+ browser observation surfaces.
+ """
+
+ browser_id: Required[str]
+ """Browser session ID, not a reusable browser name."""
+
+ type: Required[Literal["fill"]]
+
+ fields: Iterable[VaultFillFieldParam]
+ """
+ Field bindings, when the item's advertised fill operation requires them. Each
+ selector is searched in the main frame and all descendant frames and must
+ resolve to exactly one editable input or select, matched directly or through a
+ containing element; zero or multiple candidates fail, and no two bindings may
+ resolve to the same element. Select elements match an option by value, not
+ label. All bindings are validated before any write, then filled in request
+ order, stopping at the first failure. If the page navigates or a target
+ disappears, fill stops rather than choosing another page or element.
+ """
+
+ page_url: str
+ """Exact current top-level page URL, including path, query, and fragment.
+
+ Must match exactly one open page in the browser; zero or multiple matches fail.
+ No prefix or glob matching. When an item allows omitting it, the browser must
+ have exactly one open page.
+ """
+
+ timeout_ms: int
+ """Total operation deadline in milliseconds, not a per-field timeout."""
diff --git a/src/kernel/types/vaults/fill_vault_item_operation_result.py b/src/kernel/types/vaults/fill_vault_item_operation_result.py
new file mode 100644
index 00000000..8f63d2d7
--- /dev/null
+++ b/src/kernel/types/vaults/fill_vault_item_operation_result.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .vault_fill_field_result import VaultFillFieldResult
+
+__all__ = ["FillVaultItemOperationResult"]
+
+
+class FillVaultItemOperationResult(BaseModel):
+ fields: List[VaultFillFieldResult]
+ """
+ One result per request binding, in request order; empty when the fill used no
+ bindings. After the first failed or unknown field, all remaining fields are
+ not_attempted.
+ """
+
+ status: Literal["completed", "failed", "unknown"]
+ """Completed when the fill finished.
+
+ Failed when execution stopped with known outcomes. Unknown when an outcome
+ cannot be determined. Completed means values were supplied to the page, not that
+ the site accepted or submitted them.
+ """
+
+ type: Literal["fill"]
diff --git a/src/kernel/types/vaults/item_events_params.py b/src/kernel/types/vaults/item_events_params.py
new file mode 100644
index 00000000..cee33414
--- /dev/null
+++ b/src/kernel/types/vaults/item_events_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ItemEventsParams"]
+
+
+class ItemEventsParams(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ after: str
+ """Return events after this event ID."""
+
+ wait: int
+ """Long-poll for new events for up to this many seconds."""
diff --git a/src/kernel/types/vaults/item_events_response.py b/src/kernel/types/vaults/item_events_response.py
new file mode 100644
index 00000000..fa1959fe
--- /dev/null
+++ b/src/kernel/types/vaults/item_events_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .vault_item_event import VaultItemEvent
+
+__all__ = ["ItemEventsResponse"]
+
+ItemEventsResponse: TypeAlias = List[VaultItemEvent]
diff --git a/src/kernel/types/vaults/item_list_response.py b/src/kernel/types/vaults/item_list_response.py
new file mode 100644
index 00000000..9bc1fb58
--- /dev/null
+++ b/src/kernel/types/vaults/item_list_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .vault_item import VaultItem
+
+__all__ = ["ItemListResponse"]
+
+ItemListResponse: TypeAlias = List[VaultItem]
diff --git a/src/kernel/types/vaults/item_perform_operation_params.py b/src/kernel/types/vaults/item_perform_operation_params.py
new file mode 100644
index 00000000..9c3cdbf1
--- /dev/null
+++ b/src/kernel/types/vaults/item_perform_operation_params.py
@@ -0,0 +1,233 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Iterable
+from datetime import datetime
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from ..._types import SequenceNotStr
+from ..._utils import PropertyInfo
+from .vault_fill_field_param import VaultFillFieldParam
+from .vault_webmcp_binding_param import VaultWebmcpBindingParam
+from .vault_checkout_context_param import VaultCheckoutContextParam
+
+__all__ = [
+ "ItemPerformOperationParams",
+ "AuthorizeVaultItemOperationRequest",
+ "RestartVerificationVaultItemOperationRequest",
+ "ConfirmTransactionVaultItemOperationRequest",
+ "CollectVaultItemOperationRequest",
+ "PrepareCheckoutVaultItemOperationRequest",
+ "FillVaultItemOperationRequest",
+ "OnePasswordRequestAccessVaultItemOperationRequest",
+ "OnePasswordPollAccessVaultItemOperationRequest",
+ "OnePasswordFillVaultItemOperationRequest",
+ "OnePasswordRecoverVaultItemOperationRequest",
+ "OnePasswordUpdateAccessTokenVaultItemOperationRequest",
+ "WebmcpInvokeVaultItemOperationRequest",
+]
+
+
+class AuthorizeVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ type: Required[Literal["authorize"]]
+
+ browser_id: str
+ """Browser session ID, not a reusable browser name.
+
+ The browser must have this vault attached.
+ """
+
+ page_url: str
+ """Exact current top-level URL of the final checkout page open in browser_id."""
+
+
+class RestartVerificationVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ type: Required[Literal["restart_verification"]]
+
+
+class ConfirmTransactionVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ amount: Required[int]
+ """Actual transaction amount in minor units, no greater than the approved limit."""
+
+ currency: Required[str]
+ """Must match the approved purchase currency."""
+
+ occurred_at: Required[Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]]
+ """Time the merchant outcome was observed."""
+
+ status: Required[Literal["APPROVED", "DECLINED", "PENDING", "ERROR", "CANCELLED"]]
+
+ transaction_type: Required[
+ Literal["PURCHASE", "AUTHORIZATION", "CAPTURE", "REFUND", "REVERSAL", "VERIFICATION", "CHARGEBACK", "FRAUD"]
+ ]
+
+ type: Required[Literal["confirm_transaction"]]
+
+
+class CollectVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ type: Required[Literal["collect"]]
+
+
+class PrepareCheckoutVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ checkout: Required[VaultCheckoutContextParam]
+ """
+ Required when preparing an unused AgentCard card for a supported checkout
+ processor. Consent is bound to this browser and declared merchant origin, not a
+ tab. Wait for the item's ready_to_submit status before native Pay and submit
+ within its readiness deadline. Unused preparations expire automatically; every
+ preparation is single-use, including after failure or expiry.
+ """
+
+ type: Required[Literal["prepare_checkout"]]
+
+
+class FillVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ browser_id: Required[str]
+ """Browser session ID, not a reusable browser name."""
+
+ type: Required[Literal["fill"]]
+
+ fields: Iterable[VaultFillFieldParam]
+ """
+ Field bindings, when the item's advertised fill operation requires them. Each
+ selector is searched in the main frame and all descendant frames and must
+ resolve to exactly one editable input or select, matched directly or through a
+ containing element; zero or multiple candidates fail, and no two bindings may
+ resolve to the same element. Select elements match an option by value, not
+ label. All bindings are validated before any write, then filled in request
+ order, stopping at the first failure. If the page navigates or a target
+ disappears, fill stops rather than choosing another page or element.
+ """
+
+ page_url: str
+ """Exact current top-level page URL, including path, query, and fragment.
+
+ Must match exactly one open page in the browser; zero or multiple matches fail.
+ No prefix or glob matching. When an item allows omitting it, the browser must
+ have exactly one open page.
+ """
+
+ timeout_ms: int
+ """Total operation deadline in milliseconds, not a per-field timeout."""
+
+
+class OnePasswordRequestAccessVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ type: Required[Literal["1pw_create_access_request"]]
+
+ goal: str
+
+ keywords: SequenceNotStr[str]
+
+ reason: str
+
+
+class OnePasswordPollAccessVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ type: Required[Literal["1pw_access_request_status"]]
+
+ timeout_seconds: int
+
+
+class OnePasswordFillVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ browser_id: Required[str]
+ """Browser session ID, not a reusable browser name."""
+
+ page_url: Required[str]
+ """Exact current top-level page URL.
+
+ Must match exactly one open page in the browser.
+ """
+
+ type: Required[Literal["1pw_fill"]]
+
+ entry_id: str
+ """ID of an approved request entry.
+
+ Required when several approved entries have the page's origin.
+ """
+
+ timeout_ms: int
+
+
+class OnePasswordRecoverVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ type: Required[Literal["1pw_recover"]]
+
+
+class OnePasswordUpdateAccessTokenVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ access_token: Required[str]
+
+ type: Required[Literal["1pw_update_access_token"]]
+
+ access_token_expires_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+ """Optional supplied expiry. Omit to clear the old expiry."""
+
+
+class WebmcpInvokeVaultItemOperationRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ bindings: Required[Iterable[VaultWebmcpBindingParam]]
+
+ browser_id: Required[str]
+ """Browser session ID, not a reusable browser name."""
+
+ input: Required[Dict[str, object]]
+ """Public tool arguments with an existing null slot at each binding path.
+
+ At most 64 KiB after JSON serialization, including substituted values. Never
+ include vault values here.
+ """
+
+ page_url: Required[str]
+ """Exact top-level URL from the discovered tool source (fragment omitted).
+
+ This pins the target page; it does not authorize a destination.
+ """
+
+ tool_ref: Required[str]
+ """Opaque reference to the exact live WebMCP registration."""
+
+ type: Required[Literal["webmcp_invoke"]]
+
+ timeout_sec: int
+ """
+ Tool invocation timeout in seconds; preflight and response handling have an
+ additional bounded allowance. An indeterminate outcome is not retried.
+ """
+
+
+ItemPerformOperationParams: TypeAlias = Union[
+ AuthorizeVaultItemOperationRequest,
+ RestartVerificationVaultItemOperationRequest,
+ ConfirmTransactionVaultItemOperationRequest,
+ CollectVaultItemOperationRequest,
+ PrepareCheckoutVaultItemOperationRequest,
+ FillVaultItemOperationRequest,
+ OnePasswordRequestAccessVaultItemOperationRequest,
+ OnePasswordPollAccessVaultItemOperationRequest,
+ OnePasswordFillVaultItemOperationRequest,
+ OnePasswordRecoverVaultItemOperationRequest,
+ OnePasswordUpdateAccessTokenVaultItemOperationRequest,
+ WebmcpInvokeVaultItemOperationRequest,
+]
diff --git a/src/kernel/types/vaults/item_retrieve_params.py b/src/kernel/types/vaults/item_retrieve_params.py
new file mode 100644
index 00000000..1903a36a
--- /dev/null
+++ b/src/kernel/types/vaults/item_retrieve_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ItemRetrieveParams"]
+
+
+class ItemRetrieveParams(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ expand: List[Literal["payment_methods"]]
+ """Live fields advertised by `available_expansions` to include in `expanded`."""
+
+ wait: int
+ """
+ Hold for up to this many seconds while the item is pending authorization,
+ approval, or credential collection. Return the current item when ready or when
+ the wait elapses. This does not wait for edits to an already-ready credential;
+ poll GET without wait and compare version to observe changes after collect.
+ Managed auth credentials are created ready, so wait returns immediately.
+ """
diff --git a/src/kernel/types/vaults/item_update_params.py b/src/kernel/types/vaults/item_update_params.py
new file mode 100644
index 00000000..33e3ad08
--- /dev/null
+++ b/src/kernel/types/vaults/item_update_params.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+from .card_vault_item_spec_param import CardVaultItemSpecParam
+from .credential_vault_item_spec_update_param import CredentialVaultItemSpecUpdateParam
+
+__all__ = ["ItemUpdateParams", "CardVaultItemUpdateRequest", "CredentialVaultItemUpdateRequest"]
+
+
+class CardVaultItemUpdateRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ spec: Required[CardVaultItemSpecParam]
+ """Live Link card request.
+
+ Test-mode card creation is not supported. The spec can be changed with PATCH
+ until the card is authorized at a checkout. Authorize it with the browser at
+ final checkout: Kernel inspects the checkout through WebMCP and internally
+ chooses a merchant-bound Link Pay Token when supported or a virtual card
+ otherwise. Callers never receive or choose how the credential is delivered.
+ """
+
+ type: Literal["card"]
+
+
+class CredentialVaultItemUpdateRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ spec: Required[CredentialVaultItemSpecUpdateParam]
+
+ type: Required[Literal["credential"]]
+
+ version: Required[int]
+ """Expected current item version from the latest read."""
+
+ expected_item_id: str
+ """Optional immutable item ID precondition.
+
+ Returns 409 if the key now identifies a different item. Accepted writes target
+ this immutable ID, preventing replacement-key races. Supply this when submitting
+ a form bound to a previously read item.
+ """
+
+
+ItemUpdateParams: TypeAlias = Union[CardVaultItemUpdateRequest, CredentialVaultItemUpdateRequest]
diff --git a/src/kernel/types/vaults/item_upsert_params.py b/src/kernel/types/vaults/item_upsert_params.py
new file mode 100644
index 00000000..04bd3f11
--- /dev/null
+++ b/src/kernel/types/vaults/item_upsert_params.py
@@ -0,0 +1,235 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+from .card_vault_item_spec_param import CardVaultItemSpecParam
+from .kernel_wallet_vault_item_spec_param import KernelWalletVaultItemSpecParam
+from .credential_vault_item_spec_input_param import CredentialVaultItemSpecInputParam
+from .one_password_credential_account_spec_param import OnePasswordCredentialAccountSpecParam
+
+__all__ = [
+ "ItemUpsertParams",
+ "WalletVaultItemRequest",
+ "WalletVaultItemRequestSpec",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpec",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorization",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationKernelManagedLinkAuthorizationInput",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationKernelManagedLinkAuthorizationInputClient",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInput",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputClient",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputClientProviderConfig",
+ "WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputTokens",
+ "WalletVaultItemRequestSpecAgentCardWalletVaultItemSpec",
+ "WalletVaultItemRequestSpecAgentCardWalletVaultItemSpecProviderConfig",
+ "CardVaultItemRequest",
+ "CredentialAccountVaultItemRequest",
+ "CredentialVaultItemRequest",
+]
+
+
+class WalletVaultItemRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ spec: Required[WalletVaultItemRequestSpec]
+ """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.
+ 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"]]
+
+
+class WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationKernelManagedLinkAuthorizationInputClient(
+ TypedDict, total=False
+):
+ type: Required[Literal["kernel_managed"]]
+
+
+class WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationKernelManagedLinkAuthorizationInput(
+ TypedDict, total=False
+):
+ """Kernel starts and completes the user's Link authorization flow."""
+
+ client: Required[
+ WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationKernelManagedLinkAuthorizationInputClient
+ ]
+
+ method: Required[Literal["oauth"]]
+
+
+class WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputClientProviderConfig(
+ TypedDict, total=False
+):
+ """Select a provider config by ID or name.
+
+ Responses return the ID. Renaming a config does not change existing wallet bindings; an item cannot switch to a different config after creation.
+ """
+
+ id: str
+
+ name: str
+
+
+class WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputClient(
+ TypedDict, total=False
+):
+ provider_config: Required[
+ WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputClientProviderConfig
+ ]
+ """Select a provider config by ID or name.
+
+ Responses return the ID. Renaming a config does not change existing wallet
+ bindings; an item cannot switch to a different config after creation.
+ """
+
+ type: Required[Literal["customer_managed"]]
+
+
+class WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputTokens(
+ TypedDict, total=False
+):
+ """Send the token pair from your backend.
+
+ Both tokens must be from the same Link grant under the referenced client. Supply a currently valid access token. Kernel refreshes when needed after import and uses the expiry returned by Link for subsequent tokens. Tokens are never returned in wallet responses, events, or logs.
+ """
+
+ access_token: Required[str]
+
+ refresh_token: Required[str]
+
+
+class WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInput(
+ TypedDict, total=False
+):
+ """The customer's backend completes Link OAuth and supplies the resulting tokens.
+
+ For a new wallet, Kernel verifies the access token can access Link payment methods without consuming or rotating the refresh token. Valid access creates a wallet with state.status=connected. An expired, invalid, revoked, or insufficiently scoped access token returns 400 and no wallet is created. Refresh expired tokens in your backend before importing them. A failed import does not modify existing wallets.
+ After successful import, Kernel owns subsequent refresh-token rotation; the customer must stop refreshing this grant. Import does not verify the refresh token: if it or the configured client credentials are rejected during a later refresh, the imported wallet becomes degraded. An unknown refresh outcome also leaves it degraded; Kernel does not retry a refresh token that may already have been consumed. There is no in-place reauthorization operation for an imported wallet.
+ If this imported wallet's credentials become unusable, obtain a fresh Link OAuth grant in your backend and create a wallet under a NEW wallet key. Use the new wallet for NEW cards and payments, not to retry an old payment whose outcome is uncertain. This does not replace the old grant, rebind existing cards, or resolve their payment outcomes. Retain the old wallet and its cards while reconciling any uncertain payments with the provider or support. Do not repeat an uncertain payment on the new wallet, and do not treat deletion as evidence that it did not execute. Deletion of the old wallet can remain blocked by unresolved child cards.
+ Repeating a create for the same item key and non-secret spec returns the existing wallet without replacing tokens, even if they have rotated or the wallet needs reconnection. ID and name references resolving to the same config are equivalent. A different config or non-secret spec returns 409. This create operation does not replace an existing grant.
+ """
+
+ client: Required[
+ WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputClient
+ ]
+
+ method: Required[Literal["oauth"]]
+
+ tokens: Required[
+ WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInputTokens
+ ]
+ """Send the token pair from your backend.
+
+ Both tokens must be from the same Link grant under the referenced client. Supply
+ a currently valid access token. Kernel refreshes when needed after import and
+ uses the expiry returned by Link for subsequent tokens. Tokens are never
+ returned in wallet responses, events, or logs.
+ """
+
+
+WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorization: TypeAlias = Union[
+ WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationKernelManagedLinkAuthorizationInput,
+ WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorizationImportedLinkAuthorizationInput,
+]
+
+
+class WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpec(TypedDict, total=False):
+ authorization: Required[WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpecAuthorization]
+ """Kernel starts and completes the user's Link authorization flow."""
+
+ provider: Required[Literal["link"]]
+
+
+class WalletVaultItemRequestSpecAgentCardWalletVaultItemSpecProviderConfig(TypedDict, total=False):
+ """Select an AgentCard configuration.
+
+ The wallet's configuration cannot be changed after creation.
+ """
+
+ id: str
+
+ name: str
+
+
+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. 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"]]
+
+ provider_config: WalletVaultItemRequestSpecAgentCardWalletVaultItemSpecProviderConfig
+ """Select an AgentCard configuration.
+
+ The wallet's configuration cannot be changed after creation.
+ """
+
+ user_id: str
+
+
+WalletVaultItemRequestSpec: TypeAlias = Union[
+ WalletVaultItemRequestSpecLinkWalletVaultItemRequestSpec,
+ WalletVaultItemRequestSpecAgentCardWalletVaultItemSpec,
+ KernelWalletVaultItemSpecParam,
+]
+
+
+class CardVaultItemRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ spec: Required[CardVaultItemSpecParam]
+ """Live Link card request.
+
+ Test-mode card creation is not supported. The spec can be changed with PATCH
+ until the card is authorized at a checkout. Authorize it with the browser at
+ final checkout: Kernel inspects the checkout through WebMCP and internally
+ chooses a merchant-bound Link Pay Token when supported or a virtual card
+ otherwise. Callers never receive or choose how the credential is delivered.
+ """
+
+ type: Required[Literal["card"]]
+
+
+class CredentialAccountVaultItemRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ spec: Required[OnePasswordCredentialAccountSpecParam]
+
+ type: Required[Literal["credential_account"]]
+
+
+class CredentialVaultItemRequest(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ spec: Required[CredentialVaultItemSpecInputParam]
+ """Credential fields are for login and other non-payment credentials.
+
+ Do not store, collect, or fill credit card data in credential items. Use wallet
+ and card item types for credit cards and payment checkout instead. Field order
+ is preserved in the user-facing collection form, so list fields in the same
+ top-to-bottom order as the website.
+ """
+
+ type: Required[Literal["credential"]]
+
+
+ItemUpsertParams: TypeAlias = Union[
+ WalletVaultItemRequest, CardVaultItemRequest, CredentialAccountVaultItemRequest, CredentialVaultItemRequest
+]
diff --git a/src/kernel/types/vaults/kernel_card_state.py b/src/kernel/types/vaults/kernel_card_state.py
new file mode 100644
index 00000000..b2daa14f
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_card_state.py
@@ -0,0 +1,70 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["KernelCardState", "Masks"]
+
+
+class Masks(BaseModel):
+ brand: Optional[str] = None
+
+ last4: Optional[str] = None
+
+ token_last4: Optional[str] = None
+ """Last four digits of the network token presented to the merchant."""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, str] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> str: ...
+ else:
+ __pydantic_extra__: Dict[str, str]
+
+
+class KernelCardState(BaseModel):
+ """A ready Kernel card supports authorize, fill, then submit checkout.
+
+ Card details are delivered for a few minutes after fill; submit promptly. Repeated fill is safe and never submits checkout. Visa purchases require a spend_approval action before details are issued; Mastercard purchases need no hosted approval. masks.last4 is the enrolled card's last four digits; masks.token_last4, when present, is the token's last four digits shown to the merchant. Kernel does not observe whether the merchant charged the card.
+ """
+
+ provider: Literal["kernel"]
+
+ status: Literal[
+ "requested", "pending_authorization", "ready", "consumed", "expired", "declined", "recovery_required"
+ ]
+ """
+ pending_authorization on a Visa purchase waits for the cardholder to approve it
+ through the spend_approval action; an unused link expires after 30 minutes.
+ recovery_required means verification, approving the purchase or issuing the
+ one-time code has an unresolved outcome. A server-classified initial attestation
+ failure before intent dispatch may advertise restart_verification to prepare a
+ fresh local verification link; this does not approve spending or replay any
+ provider request. Unclassified legacy recovery and uncertain intent creation
+ cannot restart. Kernel never approves again or issues another code for the item
+ automatically. To continue, delete this card and create a new purchase; the
+ cardholder approves the new purchase separately. Recovery while reporting a
+ merchant transaction cannot be deleted and must be reconciled with support. When
+ status_reason says the provider refused retrieval before acceptance, no code was
+ issued and a later read retries. declined means the card network refused to
+ issue a code.
+ """
+
+ domains: Optional[List[str]] = None
+ """Informational registrable domain.
+
+ Fill is locked to merchant_url's exact origin.
+ """
+
+ masks: Optional[Masks] = None
+
+ status_reason: Optional[str] = None
diff --git a/src/kernel/types/vaults/kernel_card_vault_item_spec.py b/src/kernel/types/vaults/kernel_card_vault_item_spec.py
new file mode 100644
index 00000000..77912828
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_card_vault_item_spec.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["KernelCardVaultItemSpec"]
+
+
+class KernelCardVaultItemSpec(BaseModel):
+ """One live purchase with a Kernel-enrolled card.
+
+ Authorization obtains an agentic network token number, expiry and one-time 3-digit code. They are stored encrypted for the fill operation, which types them only on merchant_url's origin; the merchant's own checkout submits the payment. The one-time code is valid until the item's expires_at; fill and submit checkout before then. Mastercard purchases need no cardholder approval. A Visa purchase returns a spend_approval action: the cardholder approves it with a Visa passkey, and Kernel registers a Visa intent for one transaction up to the amount before issuing the code. For supported cards whose network cannot be enrolled for agentic tokens, authorize prepares checkout details without issuing a one-time code. The card can be filled into one checkout before its expires_at, one hour after authorization. Fill types those details on merchant_url's origin. The amount, merchant_url and merchant_country are recorded but do not restrict payment requests sent by the browser. Card updates are not supported; delete and create a new item instead.
+ """
+
+ amount: int
+ """
+ Integer amount in minor currency units (at most 1000000), bound to the one-time
+ code.
+ """
+
+ currency: str
+ """ISO 4217 code.
+
+ Supported: aud, brl, cad, chf, czk, dkk, eur, gbp, hkd, inr, jpy, krw, mxn, nok,
+ nzd, pln, sek, sgd, usd, zar.
+ """
+
+ merchant_name: str
+
+ merchant_url: str
+ """Merchant checkout URL. Fill is allowed only on this URL's origin."""
+
+ provider: Literal["kernel"]
+
+ wallet: str
+ """Key of the Kernel wallet item whose enrolled card pays."""
+
+ merchant_category: Optional[str] = None
+ """Actual merchant category when known. Omit rather than invent a category."""
+
+ merchant_category_code: Optional[str] = None
+ """Actual merchant MCC when known.
+
+ Omit rather than invent a code. 0000 is not accepted.
+ """
+
+ merchant_country: Optional[str] = None
+ """The merchant's ISO 3166-1 alpha-2 country code.
+
+ Required for Visa cards, whose one-time code is issued for the merchant's
+ country.
+ """
diff --git a/src/kernel/types/vaults/kernel_card_vault_item_spec_param.py b/src/kernel/types/vaults/kernel_card_vault_item_spec_param.py
new file mode 100644
index 00000000..77d3084d
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_card_vault_item_spec_param.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["KernelCardVaultItemSpecParam"]
+
+
+class KernelCardVaultItemSpecParam(TypedDict, total=False):
+ """One live purchase with a Kernel-enrolled card.
+
+ Authorization obtains an agentic network token number, expiry and one-time 3-digit code. They are stored encrypted for the fill operation, which types them only on merchant_url's origin; the merchant's own checkout submits the payment. The one-time code is valid until the item's expires_at; fill and submit checkout before then. Mastercard purchases need no cardholder approval. A Visa purchase returns a spend_approval action: the cardholder approves it with a Visa passkey, and Kernel registers a Visa intent for one transaction up to the amount before issuing the code. For supported cards whose network cannot be enrolled for agentic tokens, authorize prepares checkout details without issuing a one-time code. The card can be filled into one checkout before its expires_at, one hour after authorization. Fill types those details on merchant_url's origin. The amount, merchant_url and merchant_country are recorded but do not restrict payment requests sent by the browser. Card updates are not supported; delete and create a new item instead.
+ """
+
+ amount: Required[int]
+ """
+ Integer amount in minor currency units (at most 1000000), bound to the one-time
+ code.
+ """
+
+ currency: Required[str]
+ """ISO 4217 code.
+
+ Supported: aud, brl, cad, chf, czk, dkk, eur, gbp, hkd, inr, jpy, krw, mxn, nok,
+ nzd, pln, sek, sgd, usd, zar.
+ """
+
+ merchant_name: Required[str]
+
+ merchant_url: Required[str]
+ """Merchant checkout URL. Fill is allowed only on this URL's origin."""
+
+ provider: Required[Literal["kernel"]]
+
+ wallet: Required[str]
+ """Key of the Kernel wallet item whose enrolled card pays."""
+
+ merchant_category: str
+ """Actual merchant category when known. Omit rather than invent a category."""
+
+ merchant_category_code: str
+ """Actual merchant MCC when known.
+
+ Omit rather than invent a code. 0000 is not accepted.
+ """
+
+ merchant_country: str
+ """The merchant's ISO 3166-1 alpha-2 country code.
+
+ Required for Visa cards, whose one-time code is issued for the merchant's
+ country.
+ """
diff --git a/src/kernel/types/vaults/kernel_credential_vault_item_spec.py b/src/kernel/types/vaults/kernel_credential_vault_item_spec.py
new file mode 100644
index 00000000..a58a11d6
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_credential_vault_item_spec.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .credential_vault_field_definition import CredentialVaultFieldDefinition
+
+__all__ = ["KernelCredentialVaultItemSpec"]
+
+
+class KernelCredentialVaultItemSpec(BaseModel):
+ fields: List[CredentialVaultFieldDefinition]
+ """
+ Ordered field definitions rendered in this order by credential collection forms.
+ """
+
+ provider: Literal["kernel"]
+
+ description: Optional[str] = None
+ """
+ Recognizable site or service name displayed verbatim as the form title, without
+ suffixes such as sign-in credentials. Display text only, not an enforced
+ destination policy.
+ """
diff --git a/src/kernel/types/vaults/kernel_credential_vault_item_spec_input_param.py b/src/kernel/types/vaults/kernel_credential_vault_item_spec_input_param.py
new file mode 100644
index 00000000..d8ae9257
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_credential_vault_item_spec_input_param.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 Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+from .credential_vault_field_input_param import CredentialVaultFieldInputParam
+
+__all__ = ["KernelCredentialVaultItemSpecInputParam"]
+
+
+class KernelCredentialVaultItemSpecInputParam(TypedDict, total=False):
+ """Credential fields are for login and other non-payment credentials.
+
+ Do not store, collect, or fill credit card data in credential items. Use wallet and card item types for credit cards and payment checkout instead. Field order is preserved in the user-facing collection form, so list fields in the same top-to-bottom order as the website.
+ """
+
+ fields: Required[Iterable[CredentialVaultFieldInputParam]]
+ """Ordered field definitions.
+
+ Use the website's top-to-bottom field order; the collection form renders this
+ order unchanged.
+ """
+
+ provider: Required[Literal["kernel"]]
+
+ description: str
+ """
+ The site's recognizable display name, used verbatim as the user-facing form
+ title (for example, Hacker News). Use only the site or service name; do not
+ append sign-in, login, credentials, or task instructions. This is display text,
+ not an enforced destination policy. At most 16 KiB in UTF-8 bytes.
+ """
diff --git a/src/kernel/types/vaults/kernel_credential_vault_item_state.py b/src/kernel/types/vaults/kernel_credential_vault_item_state.py
new file mode 100644
index 00000000..48a85af0
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_credential_vault_item_state.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .credential_vault_field_state import CredentialVaultFieldState
+
+__all__ = ["KernelCredentialVaultItemState"]
+
+
+class KernelCredentialVaultItemState(BaseModel):
+ fields: Dict[str, CredentialVaultFieldState]
+ """Exactly one entry for each declared field."""
+
+ provider: Literal["kernel"]
+
+ status: Literal["pending_collection", "ready"]
+ """Ready means all required fields have values, not that a login succeeded.
+
+ Optional fields may remain unset.
+ """
diff --git a/src/kernel/types/vaults/kernel_wallet_state.py b/src/kernel/types/vaults/kernel_wallet_state.py
new file mode 100644
index 00000000..a35239d7
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_wallet_state.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["KernelWalletState"]
+
+
+class KernelWalletState(BaseModel):
+ provider: Literal["kernel"]
+
+ status: Literal["pending_authorization", "connected", "reconnect_required", "degraded"]
+ """pending_authorization asks the cardholder to use the card_enrollment action.
+
+ connected means the card is stored; it can pay once payment_methods reports
+ capabilities.single_use_card.eligible. reconnect_required asks the cardholder to
+ use a new card_enrollment action after an uncertain enrollment was safely
+ removed. degraded means the enrollment outcome is unknown and the wallet must be
+ deleted before adding another card.
+ """
+
+ status_reason: Optional[str] = None
diff --git a/src/kernel/types/vaults/kernel_wallet_vault_item_spec.py b/src/kernel/types/vaults/kernel_wallet_vault_item_spec.py
new file mode 100644
index 00000000..fb2f039e
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_wallet_vault_item_spec.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["KernelWalletVaultItemSpec"]
+
+
+class KernelWalletVaultItemSpec(BaseModel):
+ """One card stored with Kernel-managed credentials.
+
+ Creation returns a card_enrollment action: the cardholder enters the card and their email on a Kernel-hosted page, and the wallet connects once the card is stored. Kernel then enrolls it for an agentic network token when the issuer supports it. The card number never reaches Kernel. The connected wallet's payment_methods expansion lists the card; capabilities.single_use_card.eligible is false, with a network_token_* reason, until the card has a network token, and authorize returns 400 for such a card unless Kernel supports checkout delivery without a network token for that card. In that case, fill types the checkout details. Visa purchases require the cardholder to complete a spend_approval action before Kernel issues a one-time code; Mastercard purchases need no hosted approval.
+ """
+
+ provider: Literal["kernel"]
diff --git a/src/kernel/types/vaults/kernel_wallet_vault_item_spec_param.py b/src/kernel/types/vaults/kernel_wallet_vault_item_spec_param.py
new file mode 100644
index 00000000..b0e87117
--- /dev/null
+++ b/src/kernel/types/vaults/kernel_wallet_vault_item_spec_param.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["KernelWalletVaultItemSpecParam"]
+
+
+class KernelWalletVaultItemSpecParam(TypedDict, total=False):
+ """One card stored with Kernel-managed credentials.
+
+ Creation returns a card_enrollment action: the cardholder enters the card and their email on a Kernel-hosted page, and the wallet connects once the card is stored. Kernel then enrolls it for an agentic network token when the issuer supports it. The card number never reaches Kernel. The connected wallet's payment_methods expansion lists the card; capabilities.single_use_card.eligible is false, with a network_token_* reason, until the card has a network token, and authorize returns 400 for such a card unless Kernel supports checkout delivery without a network token for that card. In that case, fill types the checkout details. Visa purchases require the cardholder to complete a spend_approval action before Kernel issues a one-time code; Mastercard purchases need no hosted approval.
+ """
+
+ provider: Required[Literal["kernel"]]
diff --git a/src/kernel/types/vaults/managed_auth_credential_vault_field.py b/src/kernel/types/vaults/managed_auth_credential_vault_field.py
new file mode 100644
index 00000000..e13171c5
--- /dev/null
+++ b/src/kernel/types/vaults/managed_auth_credential_vault_field.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+from .credential_vault_field_type import CredentialVaultFieldType
+
+__all__ = ["ManagedAuthCredentialVaultField"]
+
+
+class ManagedAuthCredentialVaultField(BaseModel):
+ type: CredentialVaultFieldType
+ """
+ Text, email, and password have form inputs; totp and passkey do not and are
+ omitted from both Kernel-hosted and customer React forms. Password, totp, and
+ passkey must be sensitive. A totp value is an RFC 4648 Base32 generator seed
+ (case-insensitive, optional trailing padding), not an otpauth URI or current
+ code. Reject invalid or empty decoded seeds. Browser fill generates an RFC 6238
+ code at execution time using HMAC-SHA1, 6 digits, and a 30-second period.
+ Preserve leading zeros; never fill the seed. Custom algorithms, digits, periods,
+ and form enrollment are unsupported. A passkey value is a JSON-encoded FIDO
+ Credential Exchange Format passkey object with unpadded base64url
+ `credentialId`, `userHandle` (at most 64 bytes), and `key` (a PKCS#8 ECDSA P-256
+ private key), a lowercase `rpId` domain, and optional `username` and
+ `userDisplayName`. Other CXF members are ignored. Passkeys are never filled into
+ pages.
+ """
diff --git a/src/kernel/types/vaults/managed_auth_credential_vault_item_spec.py b/src/kernel/types/vaults/managed_auth_credential_vault_item_spec.py
new file mode 100644
index 00000000..b5ec8bb2
--- /dev/null
+++ b/src/kernel/types/vaults/managed_auth_credential_vault_item_spec.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["ManagedAuthCredentialVaultItemSpec"]
+
+
+class ManagedAuthCredentialVaultItemSpec(BaseModel):
+ connection_id: str
+ """ID of the managed auth connection whose saved credential this item reads.
+
+ List connections with `GET /auth/connections`.
+ """
+
+ provider: Literal["managed_auth"]
+
+ description: Optional[str] = None
+ """Display text supplied when the item was created. Omitted when none was given."""
diff --git a/src/kernel/types/vaults/managed_auth_credential_vault_item_spec_input_param.py b/src/kernel/types/vaults/managed_auth_credential_vault_item_spec_input_param.py
new file mode 100644
index 00000000..26a08fe7
--- /dev/null
+++ b/src/kernel/types/vaults/managed_auth_credential_vault_item_spec_input_param.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ManagedAuthCredentialVaultItemSpecInputParam"]
+
+
+class ManagedAuthCredentialVaultItemSpecInputParam(TypedDict, total=False):
+ """A credential backed by a managed auth connection.
+
+ The item stores no values:
+ it reads the connection's saved credential at fill time, so updates made
+ through managed auth apply immediately. The connection must be in the vault's
+ project and hold a saved Kernel credential. The check is the saved credential,
+ not connection status: a connection that needs re-authentication qualifies,
+ and an authenticated one without a saved credential does not. Returns 404 for
+ an unknown connection, and 409 when the connection is in another project, has
+ no saved credential yet, or uses an external credential provider such as
+ 1Password. No collection form is offered; managed auth collects the login.
+ """
+
+ connection_id: Required[str]
+ """ID of the managed auth connection whose saved credential this item reads.
+
+ List connections with `GET /auth/connections`.
+ """
+
+ provider: Required[Literal["managed_auth"]]
+
+ description: str
+ """Optional display text for the item, such as the site or service name.
+
+ Set at creation and cannot be changed later; repeating the request with a
+ different description returns 409. At most 16 KiB in UTF-8 bytes.
+ """
diff --git a/src/kernel/types/vaults/managed_auth_credential_vault_item_state.py b/src/kernel/types/vaults/managed_auth_credential_vault_item_state.py
new file mode 100644
index 00000000..3d66ef5e
--- /dev/null
+++ b/src/kernel/types/vaults/managed_auth_credential_vault_item_state.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .managed_auth_credential_vault_field import ManagedAuthCredentialVaultField
+
+__all__ = ["ManagedAuthCredentialVaultItemState"]
+
+
+class ManagedAuthCredentialVaultItemState(BaseModel):
+ provider: Literal["managed_auth"]
+
+ status: Literal["ready", "unavailable"]
+ """
+ Items are created ready, which means the connection has a saved Kernel
+ credential that stores values or a TOTP seed (what has_values or has_totp_secret
+ report on credentials), not that a login succeeded. Unavailable means it no
+ longer does; such items advertise no operations.
+ """
+
+ fields: Optional[Dict[str, ManagedAuthCredentialVaultField]] = None
+ """
+ One entry per non-empty value on the connection's saved credential, keyed by the
+ field name to use in fill bindings. Included on single-item responses (create
+ and get) and omitted from list responses, like `value_keys` on credentials.
+ Empty when unavailable or when every stored value is empty. A stored value named
+ totp_secret is never listed or filled. Values are never returned. A totp entry
+ is present when the credential has a TOTP seed; fill writes a generated code for
+ it. Entries follow the connection's credential.
+ """
+
+ status_reason: Optional[Literal["connection_not_found", "no_credential", "external_credential"]] = None
+ """Present when status is unavailable.
+
+ connection_not_found means the connection was deleted. no_credential means the
+ connection's credential was deleted or emptied after the item was created.
+ external_credential means the connection was moved to an external credential
+ provider.
+ """
diff --git a/src/kernel/types/vaults/one_password_credential_account_spec.py b/src/kernel/types/vaults/one_password_credential_account_spec.py
new file mode 100644
index 00000000..cea6ee4d
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_credential_account_spec.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["OnePasswordCredentialAccountSpec", "Authorization", "AuthorizationClient"]
+
+
+class AuthorizationClient(BaseModel):
+ type: Literal["kernel_managed"]
+
+
+class Authorization(BaseModel):
+ client: AuthorizationClient
+
+ method: Literal["oauth"]
+
+
+class OnePasswordCredentialAccountSpec(BaseModel):
+ authorization: Authorization
+
+ provider: Literal["1password"]
diff --git a/src/kernel/types/vaults/one_password_credential_account_spec_param.py b/src/kernel/types/vaults/one_password_credential_account_spec_param.py
new file mode 100644
index 00000000..12bf6023
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_credential_account_spec_param.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["OnePasswordCredentialAccountSpecParam", "Authorization", "AuthorizationClient"]
+
+
+class AuthorizationClient(TypedDict, total=False):
+ type: Required[Literal["kernel_managed"]]
+
+
+class Authorization(TypedDict, total=False):
+ client: Required[AuthorizationClient]
+
+ method: Required[Literal["oauth"]]
+
+
+class OnePasswordCredentialAccountSpecParam(TypedDict, total=False):
+ authorization: Required[Authorization]
+
+ provider: Required[Literal["1password"]]
diff --git a/src/kernel/types/vaults/one_password_credential_account_state.py b/src/kernel/types/vaults/one_password_credential_account_state.py
new file mode 100644
index 00000000..07eab56b
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_credential_account_state.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["OnePasswordCredentialAccountState"]
+
+
+class OnePasswordCredentialAccountState(BaseModel):
+ provider: Literal["1password"]
+
+ status: Literal["pending_authorization", "connected", "reconnect_required", "declined"]
+
+ status_reason: Optional[str] = None
diff --git a/src/kernel/types/vaults/one_password_credential_vault_item_spec.py b/src/kernel/types/vaults/one_password_credential_vault_item_spec.py
new file mode 100644
index 00000000..84811174
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_credential_vault_item_spec.py
@@ -0,0 +1,57 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["OnePasswordCredentialVaultItemSpec", "Requests", "RequestsEntry", "RequestsEntryParameters"]
+
+
+class RequestsEntryParameters(BaseModel):
+ website: str
+
+
+class RequestsEntry(BaseModel):
+ parameters: RequestsEntryParameters
+
+ type: str
+ """Must be login."""
+
+ keywords: Optional[List[str]] = None
+
+ reason: Optional[str] = None
+
+
+class Requests(BaseModel):
+ """Credential Request v2 input sent to the extension.
+
+ A credential item may request up to five login entries.
+ """
+
+ entries: List[RequestsEntry]
+
+ version: int
+ """Must be 2."""
+
+ goal: Optional[str] = None
+
+
+class OnePasswordCredentialVaultItemSpec(BaseModel):
+ """
+ Stored-token credentials omit account and never return access_token or integration_key.
+ """
+
+ provider: Literal["1password"]
+
+ requests: Requests
+ """Credential Request v2 input sent to the extension.
+
+ A credential item may request up to five login entries.
+ """
+
+ access_token_expires_at: Optional[datetime] = None
+ """Customer-supplied expiry metadata, if provided."""
+
+ account: Optional[str] = None
diff --git a/src/kernel/types/vaults/one_password_credential_vault_item_spec_input_param.py b/src/kernel/types/vaults/one_password_credential_vault_item_spec_input_param.py
new file mode 100644
index 00000000..3a2b4c19
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_credential_vault_item_spec_input_param.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable
+from datetime import datetime
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._types import SequenceNotStr
+from ..._utils import PropertyInfo
+
+__all__ = ["OnePasswordCredentialVaultItemSpecInputParam", "Requests", "RequestsEntry", "RequestsEntryParameters"]
+
+
+class RequestsEntryParameters(TypedDict, total=False):
+ website: Required[str]
+
+
+class RequestsEntry(TypedDict, total=False):
+ parameters: Required[RequestsEntryParameters]
+
+ type: Required[str]
+ """Must be login."""
+
+ keywords: SequenceNotStr[str]
+
+ reason: str
+
+
+class Requests(TypedDict, total=False):
+ """Credential Request v2 input sent to the extension.
+
+ A credential item may request up to five login entries.
+ """
+
+ entries: Required[Iterable[RequestsEntry]]
+
+ version: Required[int]
+ """Must be 2."""
+
+ goal: str
+
+
+class OnePasswordCredentialVaultItemSpecInputParam(TypedDict, total=False):
+ """
+ A login request backed by a connected 1Password account or by a customer-supplied access token and matching integration key. Supply either account or both secrets, never both. Supplied secrets are write-only and never returned. Supply requests for new items; website remains supported for existing account-backed callers.
+ """
+
+ provider: Required[Literal["1password"]]
+
+ access_token: str
+ """Customer-supplied 1Password broker token.
+
+ Requires integration_key; stored encrypted on this item. Omit if providing a
+ connected credential_account item via the account field.
+ """
+
+ access_token_expires_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+ """Optional supplied token expiry metadata for stored-token credentials.
+
+ Omit if providing a connected credential_account item via the account field.
+ """
+
+ account: str
+ """Key of a connected credential_account item in the same vault.
+
+ Omit for stored-token credentials.
+ """
+
+ integration_key: str
+ """Matching customer-supplied integration key.
+
+ Requires access_token; stored encrypted on this item. Omit if providing a
+ connected credential_account item via the account field.
+ """
+
+ requests: Requests
+ """Credential Request v2 input sent to the extension.
+
+ A credential item may request up to five login entries.
+ """
+
+ website: str
+ """Legacy single-login shorthand. Supply requests instead."""
diff --git a/src/kernel/types/vaults/one_password_credential_vault_item_state.py b/src/kernel/types/vaults/one_password_credential_vault_item_state.py
new file mode 100644
index 00000000..5cf9b390
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_credential_vault_item_state.py
@@ -0,0 +1,125 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "OnePasswordCredentialVaultItemState",
+ "AccessRequest",
+ "AccessRequestEntry",
+ "AccessRequestEntryParameters",
+ "AccessRequestRequest",
+ "AccessRequestRequestEntry",
+ "AccessRequestRequestEntryParameters",
+]
+
+
+class AccessRequestEntryParameters(BaseModel):
+ website: Optional[str] = None
+
+
+class AccessRequestEntry(BaseModel):
+ id: Optional[str] = None
+
+ keywords: Optional[List[str]] = None
+
+ parameters: Optional[AccessRequestEntryParameters] = None
+
+ reason: Optional[str] = None
+
+ type: Optional[str] = None
+
+
+class AccessRequestRequestEntryParameters(BaseModel):
+ website: Optional[str] = None
+
+
+class AccessRequestRequestEntry(BaseModel):
+ id: Optional[str] = None
+
+ keywords: Optional[List[str]] = None
+
+ parameters: Optional[AccessRequestRequestEntryParameters] = None
+
+ reason: Optional[str] = None
+
+ type: Optional[str] = None
+
+
+class AccessRequestRequest(BaseModel):
+ """The request object if returned by the extension.
+
+ The observed create response may omit entries; no entry IDs are invented.
+ """
+
+ entries: Optional[List[AccessRequestRequestEntry]] = None
+
+ goal: Optional[str] = None
+
+ version: Optional[int] = None
+
+
+class AccessRequest(BaseModel):
+ """Non-secret broker state.
+
+ Granted credential references stay encrypted server-side and can only be used by the fill operation.
+ """
+
+ id: str
+
+ has_autofill_token: bool
+
+ state: str
+ """One of pending, resolved, denied, or failed."""
+
+ created_at: Optional[str] = FieldInfo(alias="createdAt", default=None)
+ """Provider-created timestamp as returned by the broker."""
+
+ entries: Optional[List[AccessRequestEntry]] = None
+ """Login entries returned directly on accessRequest by the observed extension
+ build.
+
+ Omitted when the provider does not supply them.
+ """
+
+ goal: Optional[str] = None
+ """Goal echoed by the observed createAccessRequest response when present."""
+
+ granted_count: Optional[int] = None
+
+ identity: Optional[str] = None
+ """Opaque provider identity returned by the broker."""
+
+ path: Optional[str] = None
+ """Provider path if supplied in the broker response."""
+
+ request: Optional[AccessRequestRequest] = None
+ """The request object if returned by the extension.
+
+ The observed create response may omit entries; no entry IDs are invented.
+ """
+
+
+class OnePasswordCredentialVaultItemState(BaseModel):
+ provider: Literal["1password"]
+
+ status: Literal["pending_authorization", "ready", "declined", "failed"]
+
+ access_request: Optional[AccessRequest] = None
+ """Non-secret broker state.
+
+ Granted credential references stay encrypted server-side and can only be used by
+ the fill operation.
+ """
+
+ access_request_id: Optional[str] = None
+ """
+ Opaque request ID returned by the 1Password broker after a successful
+ createAccessRequest call.
+ """
+
+ status_reason: Optional[str] = None
diff --git a/src/kernel/types/vaults/one_password_fill_vault_item_operation_request_param.py b/src/kernel/types/vaults/one_password_fill_vault_item_operation_request_param.py
new file mode 100644
index 00000000..f36a456c
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_fill_vault_item_operation_request_param.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["OnePasswordFillVaultItemOperationRequestParam"]
+
+
+class OnePasswordFillVaultItemOperationRequestParam(TypedDict, total=False):
+ """Fill and submit an approved 1Password login in the selected browser page.
+
+ The page must share the selected entry's login origin. Supply entry_id when more than one approved entry matches the page origin. The extension selects fields; callers cannot supply selectors or secret values. Submission does not confirm website authentication.
+ """
+
+ browser_id: Required[str]
+ """Browser session ID, not a reusable browser name."""
+
+ page_url: Required[str]
+ """Exact current top-level page URL.
+
+ Must match exactly one open page in the browser.
+ """
+
+ type: Required[Literal["1pw_fill"]]
+
+ entry_id: str
+ """ID of an approved request entry.
+
+ Required when several approved entries have the page's origin.
+ """
+
+ timeout_ms: int
diff --git a/src/kernel/types/vaults/one_password_fill_vault_item_operation_result.py b/src/kernel/types/vaults/one_password_fill_vault_item_operation_result.py
new file mode 100644
index 00000000..9fa64aeb
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_fill_vault_item_operation_result.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["OnePasswordFillVaultItemOperationResult"]
+
+
+class OnePasswordFillVaultItemOperationResult(BaseModel):
+ """The submission result reported by the 1Password extension when available.
+
+ Kernel returns fill_unknown if the extension call has no conclusive result. Inspect the page to determine successful authentication on the website.
+ """
+
+ status: Literal["fill_submitted", "fill_failed", "fill_unknown"]
+ """Kernel's outcome of the extension call.
+
+ fill_submitted means the extension reported submission, not website
+ authentication. fill_failed means the extension returned a known failure and
+ includes error_code. fill_unknown means the form may have been filled or
+ submitted without a conclusive result; inspect the page to see the result of the
+ fill.
+ """
+
+ type: Literal["1pw_fill"]
+
+ error_code: Optional[Literal["fillFailed", "autosubmitFailed", "noExistingCredentials", "authenticationFailed"]] = (
+ None
+ )
+ """
+ Present for fill_failed, and for fill_unknown when the extension reported
+ autosubmitFailed (it filled and submitted the form but could not confirm the
+ fill finished). These are allowlisted 1Password extension codes, never raw
+ errors, secrets, or page content.
+ """
diff --git a/src/kernel/types/vaults/one_password_oauth_action.py b/src/kernel/types/vaults/one_password_oauth_action.py
new file mode 100644
index 00000000..b2e4d377
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_oauth_action.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["OnePasswordOAuthAction"]
+
+
+class OnePasswordOAuthAction(BaseModel):
+ name: Literal["1password_oauth"]
+
+ url: str
+ """1Password-hosted OAuth authorization URL for the human to open."""
diff --git a/src/kernel/types/vaults/one_password_recover_vault_item_operation_request_param.py b/src/kernel/types/vaults/one_password_recover_vault_item_operation_request_param.py
new file mode 100644
index 00000000..5913ab40
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_recover_vault_item_operation_request_param.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["OnePasswordRecoverVaultItemOperationRequestParam"]
+
+
+class OnePasswordRecoverVaultItemOperationRequestParam(TypedDict, total=False):
+ """Kernel encountered a recoverable error while linking this 1Password account.
+
+ Use this action to get a new link to recover the connection. After recovery completes, start a new authorization on the same item.
+ """
+
+ type: Required[Literal["1pw_recover"]]
diff --git a/src/kernel/types/vaults/one_password_request_access_vault_item_operation_request_param.py b/src/kernel/types/vaults/one_password_request_access_vault_item_operation_request_param.py
new file mode 100644
index 00000000..f856f17f
--- /dev/null
+++ b/src/kernel/types/vaults/one_password_request_access_vault_item_operation_request_param.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["OnePasswordRequestAccessVaultItemOperationRequestParam"]
+
+
+class OnePasswordRequestAccessVaultItemOperationRequestParam(TypedDict, total=False):
+ """
+ Request access to login entries in the end-user's own, non-shared 1Password vault. No browser is needed; the end-user approves access in the 1Password app. Shared-vault items and passkeys are not supported. Per-entry reason and keywords overrides are only supported for a single login entry.
+ """
+
+ type: Required[Literal["1pw_create_access_request"]]
+
+ goal: str
+
+ keywords: SequenceNotStr[str]
+
+ reason: str
diff --git a/src/kernel/types/vaults/prepare_checkout_vault_item_operation_request_param.py b/src/kernel/types/vaults/prepare_checkout_vault_item_operation_request_param.py
new file mode 100644
index 00000000..73d92e08
--- /dev/null
+++ b/src/kernel/types/vaults/prepare_checkout_vault_item_operation_request_param.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+from .vault_checkout_context_param import VaultCheckoutContextParam
+
+__all__ = ["PrepareCheckoutVaultItemOperationRequestParam"]
+
+
+class PrepareCheckoutVaultItemOperationRequestParam(TypedDict, total=False):
+ """Prepare an unused AgentCard card for a supported checkout.
+
+ Deliver the returned approval URL and keep the approval page open. Poll the item until ready_to_submit, then submit native Pay before preparation.expires_at. Readiness lasts at most 30 seconds. Unused preparations expire automatically. Preparations are single-use even after failure or expiry; do not automatically retry and reconcile uncertain outcomes with the merchant.
+ """
+
+ checkout: Required[VaultCheckoutContextParam]
+ """
+ Required when preparing an unused AgentCard card for a supported checkout
+ processor. Consent is bound to this browser and declared merchant origin, not a
+ tab. Wait for the item's ready_to_submit status before native Pay and submit
+ within its readiness deadline. Unused preparations expire automatically; every
+ preparation is single-use, including after failure or expiry.
+ """
+
+ type: Required[Literal["prepare_checkout"]]
diff --git a/src/kernel/types/vaults/restart_verification_vault_item_operation_request_param.py b/src/kernel/types/vaults/restart_verification_vault_item_operation_request_param.py
new file mode 100644
index 00000000..89c4e3db
--- /dev/null
+++ b/src/kernel/types/vaults/restart_verification_vault_item_operation_request_param.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, Required, TypedDict
+
+__all__ = ["RestartVerificationVaultItemOperationRequestParam"]
+
+
+class RestartVerificationVaultItemOperationRequestParam(TypedDict, total=False):
+ """
+ Explicitly prepare a fresh local verification session for the same Kernel Visa purchase and connected wallet, only when restart_verification is advertised. Kernel must have durable proof of an initial attestation failure before intent dispatch. Replaces the spend_approval capability and discards session-bound verification data without calling the provider or granting spend consent. The cardholder must use the new link, verify again and explicitly approve the purchase. Unknown intent creation, legacy unclassified recovery, enrollment completion, issuance and confirmation cannot restart. Do not automatically retry this operation.
+ """
+
+ type: Required[Literal["restart_verification"]]
diff --git a/src/kernel/types/vaults/vault_card_aliases.py b/src/kernel/types/vaults/vault_card_aliases.py
new file mode 100644
index 00000000..149de39f
--- /dev/null
+++ b/src/kernel/types/vaults/vault_card_aliases.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["VaultCardAliases"]
+
+
+class VaultCardAliases(BaseModel):
+ cvc: str
+
+ exp_month: str
+
+ exp_year: str
+
+ number: str
diff --git a/src/kernel/types/vaults/vault_checkout_context_param.py b/src/kernel/types/vaults/vault_checkout_context_param.py
new file mode 100644
index 00000000..545f2dff
--- /dev/null
+++ b/src/kernel/types/vaults/vault_checkout_context_param.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+from .agentcard_prepared_processor import AgentcardPreparedProcessor
+
+__all__ = ["VaultCheckoutContextParam"]
+
+
+class VaultCheckoutContextParam(TypedDict, total=False):
+ """
+ Required when preparing an unused AgentCard card for a supported checkout processor. Consent is bound to this browser and declared merchant origin, not a tab. Wait for the item's ready_to_submit status before native Pay and submit within its readiness deadline. Unused preparations expire automatically; every preparation is single-use, including after failure or expiry.
+ """
+
+ browser_id: Required[str]
+ """Active browser session with this vault bound to it."""
+
+ environment: Required[Literal["production", "sandbox", "shared"]]
+ """
+ Use production or sandbox for Square, Braintree, Worldpay and Adyen; shared for
+ Bambora and Mercado Pago. Shared endpoints do not establish test mode. Merchant
+ credentials/configuration determine processor test mode, independently of the
+ AgentCard credential mode.
+ """
+
+ merchant_origin: Required[str]
+ """
+ Canonical HTTPS origin of the top-level merchant document, not a processor
+ iframe. HTTP localhost is accepted for tests.
+ """
+
+ psp: AgentcardPreparedProcessor
+ """Checkout processor.
+
+ Omit for Square compatibility. Adyen supports fresh-card Sessions requests on
+ Adyen hosts only. Use public dummy card fields, not vault aliases. The unique
+ armed preparation is associated with the subsequent eligible request from this
+ browser and declared merchant origin; competing preparations are rejected.
+ """
diff --git a/src/kernel/types/vaults/vault_fill_field_param.py b/src/kernel/types/vaults/vault_fill_field_param.py
new file mode 100644
index 00000000..99e0c107
--- /dev/null
+++ b/src/kernel/types/vaults/vault_fill_field_param.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["VaultFillFieldParam"]
+
+
+class VaultFillFieldParam(TypedDict, total=False):
+ field: Required[str]
+ """
+ A credential field name from the item's declared fields (Kernel) or state.fields
+ from a single-item read (managed auth), or a supported card field. Unset
+ credential fields cannot be filled.
+ """
+
+ selector: Required[str]
+
+ format: Literal["MM/YY", "MM/YYYY"]
+ """Required only for a card's combined expiration field.
+
+ Forbidden for other card fields and all credential fields.
+ """
diff --git a/src/kernel/types/vaults/vault_fill_field_result.py b/src/kernel/types/vaults/vault_fill_field_result.py
new file mode 100644
index 00000000..757b4d43
--- /dev/null
+++ b/src/kernel/types/vaults/vault_fill_field_result.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["VaultFillFieldResult"]
+
+
+class VaultFillFieldResult(BaseModel):
+ index: int
+ """Zero-based index into the request fields array."""
+
+ status: Literal["filled", "failed", "not_attempted", "unknown"]
+ """
+ Filled means the fill action completed, not that the website retained or
+ accepted the value.
+ """
+
+ error_code: Optional[
+ Literal[
+ "target_changed",
+ "element_not_found",
+ "ambiguous_selector",
+ "element_not_editable",
+ "option_not_found",
+ "timeout",
+ "execution_failed",
+ ]
+ ] = None
+ """Present only for failed or unknown fields.
+
+ Never includes secret values, DOM content, or raw browser errors.
+ """
diff --git a/src/kernel/types/vaults/vault_item.py b/src/kernel/types/vaults/vault_item.py
new file mode 100644
index 00000000..45d2f021
--- /dev/null
+++ b/src/kernel/types/vaults/vault_item.py
@@ -0,0 +1,192 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .vault_item_action import VaultItemAction
+from .card_vault_item_spec import CardVaultItemSpec
+from .vault_payment_method import VaultPaymentMethod
+from .card_vault_item_state import CardVaultItemState
+from .credential_vault_item import CredentialVaultItem
+from .wallet_vault_item_spec import WalletVaultItemSpec
+from .wallet_vault_item_state import WalletVaultItemState
+from .credential_account_vault_item import CredentialAccountVaultItem
+
+__all__ = [
+ "VaultItem",
+ "WalletVaultItem",
+ "WalletVaultItemAvailableExpansion",
+ "WalletVaultItemAvailableOperation",
+ "WalletVaultItemExpanded",
+ "CardVaultItem",
+ "CardVaultItemAvailableExpansion",
+ "CardVaultItemAvailableOperation",
+]
+
+
+class WalletVaultItemAvailableExpansion(BaseModel):
+ """
+ Live data that can currently be requested by passing its type to the item GET expand parameter.
+ """
+
+ description: str
+
+ type: Literal["payment_methods"]
+
+
+class WalletVaultItemAvailableOperation(BaseModel):
+ """An operation that is currently valid for this item.
+
+ Read the description before invoking it through the item operations endpoint.
+ """
+
+ description: str
+
+ type: Literal[
+ "authorize",
+ "restart_verification",
+ "confirm_transaction",
+ "collect",
+ "prepare_checkout",
+ "fill",
+ "1pw_create_access_request",
+ "1pw_access_request_status",
+ "1pw_fill",
+ "1pw_recover",
+ "1pw_update_access_token",
+ "webmcp_invoke",
+ ]
+
+
+class WalletVaultItemExpanded(BaseModel):
+ """Live, non-persisted data requested through the item GET expand parameter."""
+
+ payment_methods: Optional[List[VaultPaymentMethod]] = None
+
+
+class WalletVaultItem(BaseModel):
+ id: str
+
+ available_expansions: List[WalletVaultItemAvailableExpansion]
+
+ available_operations: List[WalletVaultItemAvailableOperation]
+
+ created_at: datetime
+
+ key: str
+ """Immutable item key assigned when the item is created."""
+
+ spec: WalletVaultItemSpec
+ """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.
+ 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
+
+ type: Literal["wallet"]
+
+ updated_at: datetime
+
+ action: Optional[VaultItemAction] = None
+
+ description: Optional[str] = None
+ """Server-generated provider and lifecycle guidance for agents."""
+
+ expanded: Optional[WalletVaultItemExpanded] = None
+ """Live, non-persisted data requested through the item GET expand parameter."""
+
+ expires_at: Optional[datetime] = None
+
+
+class CardVaultItemAvailableExpansion(BaseModel):
+ """
+ Live data that can currently be requested by passing its type to the item GET expand parameter.
+ """
+
+ description: str
+
+ type: Literal["payment_methods"]
+
+
+class CardVaultItemAvailableOperation(BaseModel):
+ """An operation that is currently valid for this item.
+
+ Read the description before invoking it through the item operations endpoint.
+ """
+
+ description: str
+
+ type: Literal[
+ "authorize",
+ "restart_verification",
+ "confirm_transaction",
+ "collect",
+ "prepare_checkout",
+ "fill",
+ "1pw_create_access_request",
+ "1pw_access_request_status",
+ "1pw_fill",
+ "1pw_recover",
+ "1pw_update_access_token",
+ "webmcp_invoke",
+ ]
+
+
+class CardVaultItem(BaseModel):
+ id: str
+
+ available_expansions: List[CardVaultItemAvailableExpansion]
+
+ available_operations: List[CardVaultItemAvailableOperation]
+
+ created_at: datetime
+
+ key: str
+ """Immutable item key assigned when the item is created."""
+
+ spec: CardVaultItemSpec
+ """Live Link card request.
+
+ Test-mode card creation is not supported. The spec can be changed with PATCH
+ until the card is authorized at a checkout. Authorize it with the browser at
+ final checkout: Kernel inspects the checkout through WebMCP and internally
+ chooses a merchant-bound Link Pay Token when supported or a virtual card
+ otherwise. Callers never receive or choose how the credential is delivered.
+ """
+
+ state: CardVaultItemState
+ """Issued Link cards retain encrypted card material for the fill operation.
+
+ Link cards do not expose aliases or support egress substitution.
+ """
+
+ type: Literal["card"]
+
+ updated_at: datetime
+
+ action: Optional[VaultItemAction] = None
+
+ expires_at: Optional[datetime] = None
+
+
+VaultItem: TypeAlias = Annotated[
+ Union[WalletVaultItem, CardVaultItem, CredentialAccountVaultItem, CredentialVaultItem],
+ PropertyInfo(discriminator="type"),
+]
diff --git a/src/kernel/types/vaults/vault_item_action.py b/src/kernel/types/vaults/vault_item_action.py
new file mode 100644
index 00000000..e3816895
--- /dev/null
+++ b/src/kernel/types/vaults/vault_item_action.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .one_password_oauth_action import OnePasswordOAuthAction
+
+__all__ = [
+ "VaultItemAction",
+ "LinkOAuthAction",
+ "SpendApprovalAction",
+ "PushApprovalAction",
+ "CollectAction",
+ "MfaAction",
+ "EmbeddedCeremonyAction",
+ "CardEnrollmentAction",
+]
+
+
+class LinkOAuthAction(BaseModel):
+ name: Literal["link_oauth"]
+
+ url: str
+
+
+class SpendApprovalAction(BaseModel):
+ name: Literal["spend_approval"]
+
+ url: str
+
+
+class PushApprovalAction(BaseModel):
+ name: Literal["push_approval"]
+
+
+class CollectAction(BaseModel):
+ name: Literal["collect"]
+
+
+class MfaAction(BaseModel):
+ name: Literal["mfa"]
+
+
+class EmbeddedCeremonyAction(BaseModel):
+ name: Literal["embedded_ceremony"]
+
+
+class CardEnrollmentAction(BaseModel):
+ name: Literal["card_enrollment"]
+
+ url: str
+
+
+VaultItemAction: TypeAlias = Annotated[
+ Union[
+ LinkOAuthAction,
+ OnePasswordOAuthAction,
+ SpendApprovalAction,
+ PushApprovalAction,
+ CollectAction,
+ MfaAction,
+ EmbeddedCeremonyAction,
+ CardEnrollmentAction,
+ ],
+ PropertyInfo(discriminator="name"),
+]
diff --git a/src/kernel/types/vaults/vault_item_event.py b/src/kernel/types/vaults/vault_item_event.py
new file mode 100644
index 00000000..2d4098be
--- /dev/null
+++ b/src/kernel/types/vaults/vault_item_event.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["VaultItemEvent"]
+
+
+class VaultItemEvent(BaseModel):
+ id: str
+
+ created_at: datetime
+
+ name: str
+
+ browser_id: Optional[str] = None
+ """Browser session associated with the event, when applicable."""
+
+ data: Optional[Dict[str, object]] = None
diff --git a/src/kernel/types/vaults/vault_item_operation_response.py b/src/kernel/types/vaults/vault_item_operation_response.py
new file mode 100644
index 00000000..a7a31733
--- /dev/null
+++ b/src/kernel/types/vaults/vault_item_operation_response.py
@@ -0,0 +1,199 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from datetime import datetime
+from typing_extensions import Literal, TypeAlias
+
+from ..._models import BaseModel
+from .vault_item_action import VaultItemAction
+from .card_vault_item_spec import CardVaultItemSpec
+from .vault_payment_method import VaultPaymentMethod
+from .card_vault_item_state import CardVaultItemState
+from .credential_vault_item import CredentialVaultItem
+from .wallet_vault_item_spec import WalletVaultItemSpec
+from .wallet_vault_item_state import WalletVaultItemState
+from .credential_account_vault_item import CredentialAccountVaultItem
+from .fill_vault_item_operation_result import FillVaultItemOperationResult
+from .webmcp_invoke_vault_item_operation_result import WebmcpInvokeVaultItemOperationResult
+from .one_password_fill_vault_item_operation_result import OnePasswordFillVaultItemOperationResult
+
+__all__ = [
+ "VaultItemOperationResponse",
+ "WalletVaultItem",
+ "WalletVaultItemAvailableExpansion",
+ "WalletVaultItemAvailableOperation",
+ "WalletVaultItemExpanded",
+ "CardVaultItem",
+ "CardVaultItemAvailableExpansion",
+ "CardVaultItemAvailableOperation",
+]
+
+
+class WalletVaultItemAvailableExpansion(BaseModel):
+ """
+ Live data that can currently be requested by passing its type to the item GET expand parameter.
+ """
+
+ description: str
+
+ type: Literal["payment_methods"]
+
+
+class WalletVaultItemAvailableOperation(BaseModel):
+ """An operation that is currently valid for this item.
+
+ Read the description before invoking it through the item operations endpoint.
+ """
+
+ description: str
+
+ type: Literal[
+ "authorize",
+ "restart_verification",
+ "confirm_transaction",
+ "collect",
+ "prepare_checkout",
+ "fill",
+ "1pw_create_access_request",
+ "1pw_access_request_status",
+ "1pw_fill",
+ "1pw_recover",
+ "1pw_update_access_token",
+ "webmcp_invoke",
+ ]
+
+
+class WalletVaultItemExpanded(BaseModel):
+ """Live, non-persisted data requested through the item GET expand parameter."""
+
+ payment_methods: Optional[List[VaultPaymentMethod]] = None
+
+
+class WalletVaultItem(BaseModel):
+ id: str
+
+ available_expansions: List[WalletVaultItemAvailableExpansion]
+
+ available_operations: List[WalletVaultItemAvailableOperation]
+
+ created_at: datetime
+
+ key: str
+ """Immutable item key assigned when the item is created."""
+
+ spec: WalletVaultItemSpec
+ """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.
+ 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
+
+ type: Literal["wallet"]
+
+ updated_at: datetime
+
+ action: Optional[VaultItemAction] = None
+
+ description: Optional[str] = None
+ """Server-generated provider and lifecycle guidance for agents."""
+
+ expanded: Optional[WalletVaultItemExpanded] = None
+ """Live, non-persisted data requested through the item GET expand parameter."""
+
+ expires_at: Optional[datetime] = None
+
+
+class CardVaultItemAvailableExpansion(BaseModel):
+ """
+ Live data that can currently be requested by passing its type to the item GET expand parameter.
+ """
+
+ description: str
+
+ type: Literal["payment_methods"]
+
+
+class CardVaultItemAvailableOperation(BaseModel):
+ """An operation that is currently valid for this item.
+
+ Read the description before invoking it through the item operations endpoint.
+ """
+
+ description: str
+
+ type: Literal[
+ "authorize",
+ "restart_verification",
+ "confirm_transaction",
+ "collect",
+ "prepare_checkout",
+ "fill",
+ "1pw_create_access_request",
+ "1pw_access_request_status",
+ "1pw_fill",
+ "1pw_recover",
+ "1pw_update_access_token",
+ "webmcp_invoke",
+ ]
+
+
+class CardVaultItem(BaseModel):
+ id: str
+
+ available_expansions: List[CardVaultItemAvailableExpansion]
+
+ available_operations: List[CardVaultItemAvailableOperation]
+
+ created_at: datetime
+
+ key: str
+ """Immutable item key assigned when the item is created."""
+
+ spec: CardVaultItemSpec
+ """Live Link card request.
+
+ Test-mode card creation is not supported. The spec can be changed with PATCH
+ until the card is authorized at a checkout. Authorize it with the browser at
+ final checkout: Kernel inspects the checkout through WebMCP and internally
+ chooses a merchant-bound Link Pay Token when supported or a virtual card
+ otherwise. Callers never receive or choose how the credential is delivered.
+ """
+
+ state: CardVaultItemState
+ """Issued Link cards retain encrypted card material for the fill operation.
+
+ Link cards do not expose aliases or support egress substitution.
+ """
+
+ type: Literal["card"]
+
+ updated_at: datetime
+
+ action: Optional[VaultItemAction] = None
+
+ expires_at: Optional[datetime] = None
+
+
+VaultItemOperationResponse: TypeAlias = Union[
+ WalletVaultItem,
+ CardVaultItem,
+ CredentialAccountVaultItem,
+ CredentialVaultItem,
+ FillVaultItemOperationResult,
+ OnePasswordFillVaultItemOperationResult,
+ WebmcpInvokeVaultItemOperationResult,
+]
diff --git a/src/kernel/types/vaults/vault_payment_method.py b/src/kernel/types/vaults/vault_payment_method.py
new file mode 100644
index 00000000..27f87ad1
--- /dev/null
+++ b/src/kernel/types/vaults/vault_payment_method.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["VaultPaymentMethod", "Capabilities", "CapabilitiesSingleUseCard", "Display"]
+
+
+class CapabilitiesSingleUseCard(BaseModel):
+ eligible: bool
+
+ reasons: List[str]
+
+
+class Capabilities(BaseModel):
+ """Provider-reported advisory capabilities.
+
+ A missing capability is unknown, not ineligible; only eligible=false is an explicit negative signal.
+ """
+
+ single_use_card: Optional[CapabilitiesSingleUseCard] = None
+
+
+class Display(BaseModel):
+ brand: Optional[str] = None
+
+ label: Optional[str] = None
+
+ last4: Optional[str] = None
+
+
+class VaultPaymentMethod(BaseModel):
+ id: str
+
+ capabilities: Capabilities
+ """Provider-reported advisory capabilities.
+
+ A missing capability is unknown, not ineligible; only eligible=false is an
+ explicit negative signal.
+ """
+
+ display: Display
+
+ is_default: bool
+
+ provider: str
+ """Provider that issued this payment-method ID."""
+
+ type: str
+ """Provider-neutral payment-method type normalized to lowercase."""
diff --git a/src/kernel/types/vaults/vault_webmcp_binding_param.py b/src/kernel/types/vaults/vault_webmcp_binding_param.py
new file mode 100644
index 00000000..cdb94f49
--- /dev/null
+++ b/src/kernel/types/vaults/vault_webmcp_binding_param.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["VaultWebmcpBindingParam"]
+
+
+class VaultWebmcpBindingParam(TypedDict, total=False):
+ field: Required[str]
+ """A declared, populated credential field or supported card field.
+
+ A TOTP field supplies a fresh code, never its seed. Passkey fields cannot be
+ filled.
+ """
+
+ input_path: Required[str]
+ """RFC 6901 JSON Pointer to an existing null value in input.
+
+ Object keys are exact; array indices must be canonical and in range. No root or
+ array-append paths. Each path and each field may occur only once.
+ """
+
+ format: str
+ """Required for card expiration (MM/YY or MM/YYYY), forbidden for other fields.
+
+ Invalid formats are rejected before invocation.
+ """
diff --git a/src/kernel/types/vaults/wallet_vault_item_spec.py b/src/kernel/types/vaults/wallet_vault_item_spec.py
new file mode 100644
index 00000000..6c4d9950
--- /dev/null
+++ b/src/kernel/types/vaults/wallet_vault_item_spec.py
@@ -0,0 +1,101 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .kernel_wallet_vault_item_spec import KernelWalletVaultItemSpec
+
+__all__ = [
+ "WalletVaultItemSpec",
+ "LinkWalletVaultItemSpec",
+ "LinkWalletVaultItemSpecAuthorization",
+ "LinkWalletVaultItemSpecAuthorizationClient",
+ "LinkWalletVaultItemSpecAuthorizationClientKernelManagedOAuthClient",
+ "LinkWalletVaultItemSpecAuthorizationClientCustomerManagedOAuthClient",
+ "LinkWalletVaultItemSpecAuthorizationClientCustomerManagedOAuthClientProviderConfig",
+ "AgentCardWalletVaultItemSpec",
+ "AgentCardWalletVaultItemSpecProviderConfig",
+]
+
+
+class LinkWalletVaultItemSpecAuthorizationClientKernelManagedOAuthClient(BaseModel):
+ type: Literal["kernel_managed"]
+
+
+class LinkWalletVaultItemSpecAuthorizationClientCustomerManagedOAuthClientProviderConfig(BaseModel):
+ """Select a provider config by ID or name.
+
+ Responses return the ID. Renaming a config does not change existing wallet bindings; an item cannot switch to a different config after creation.
+ """
+
+ id: Optional[str] = None
+
+ name: Optional[str] = None
+
+
+class LinkWalletVaultItemSpecAuthorizationClientCustomerManagedOAuthClient(BaseModel):
+ provider_config: LinkWalletVaultItemSpecAuthorizationClientCustomerManagedOAuthClientProviderConfig
+ """Select a provider config by ID or name.
+
+ Responses return the ID. Renaming a config does not change existing wallet
+ bindings; an item cannot switch to a different config after creation.
+ """
+
+ type: Literal["customer_managed"]
+
+
+LinkWalletVaultItemSpecAuthorizationClient: TypeAlias = Annotated[
+ Union[
+ LinkWalletVaultItemSpecAuthorizationClientKernelManagedOAuthClient,
+ LinkWalletVaultItemSpecAuthorizationClientCustomerManagedOAuthClient,
+ ],
+ PropertyInfo(discriminator="type"),
+]
+
+
+class LinkWalletVaultItemSpecAuthorization(BaseModel):
+ client: LinkWalletVaultItemSpecAuthorizationClient
+
+ method: Literal["oauth"]
+
+
+class LinkWalletVaultItemSpec(BaseModel):
+ authorization: LinkWalletVaultItemSpecAuthorization
+
+ provider: Literal["link"]
+
+
+class AgentCardWalletVaultItemSpecProviderConfig(BaseModel):
+ """Select an AgentCard configuration.
+
+ The wallet's configuration cannot be changed after creation.
+ """
+
+ id: Optional[str] = None
+
+ name: Optional[str] = None
+
+
+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. 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"]
+
+ provider_config: Optional[AgentCardWalletVaultItemSpecProviderConfig] = None
+ """Select an AgentCard configuration.
+
+ The wallet's configuration cannot be changed after creation.
+ """
+
+ user_id: Optional[str] = None
+
+
+WalletVaultItemSpec: TypeAlias = Annotated[
+ Union[LinkWalletVaultItemSpec, AgentCardWalletVaultItemSpec, KernelWalletVaultItemSpec],
+ PropertyInfo(discriminator="provider"),
+]
diff --git a/src/kernel/types/vaults/wallet_vault_item_state.py b/src/kernel/types/vaults/wallet_vault_item_state.py
new file mode 100644
index 00000000..e91b08d8
--- /dev/null
+++ b/src/kernel/types/vaults/wallet_vault_item_state.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias
+
+from ..._utils import PropertyInfo
+from ..._models import BaseModel
+from .kernel_wallet_state import KernelWalletState
+
+__all__ = ["WalletVaultItemState", "LinkWalletState", "AgentCardWalletState"]
+
+
+class LinkWalletState(BaseModel):
+ provider: Literal["link"]
+
+ status: Literal["pending_authorization", "connected", "declined", "reconnect_required", "degraded"]
+
+ status_reason: Optional[str] = None
+
+
+class AgentCardWalletState(BaseModel):
+ provider: Literal["agentcard"]
+
+ status: Literal["pending_authorization", "connected", "degraded"]
+
+ status_reason: Optional[str] = None
+
+ user_id: Optional[str] = None
+ """AgentCard user id linked to this wallet. Present once connected."""
+
+
+WalletVaultItemState: TypeAlias = Annotated[
+ Union[LinkWalletState, AgentCardWalletState, KernelWalletState], PropertyInfo(discriminator="provider")
+]
diff --git a/src/kernel/types/vaults/webmcp_invoke_vault_item_operation_request_param.py b/src/kernel/types/vaults/webmcp_invoke_vault_item_operation_request_param.py
new file mode 100644
index 00000000..21d85e6e
--- /dev/null
+++ b/src/kernel/types/vaults/webmcp_invoke_vault_item_operation_request_param.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+from .vault_webmcp_binding_param import VaultWebmcpBindingParam
+
+__all__ = ["WebmcpInvokeVaultItemOperationRequestParam"]
+
+
+class WebmcpInvokeVaultItemOperationRequestParam(TypedDict, total=False):
+ """Invoke a WebMCP tool using values from a vaulted item.
+
+ The browser must be
+ attached to the item's vault. Discover the tool_ref, inputSchema, and source
+ with GET /browsers/{id_or_name}/webmcp/tools or webmcp.listTools() in the
+ Browser REPL (POST /browsers/{id_or_name}/repl) before invoking it.
+ Input paths replace existing null slots in input. Tool output is returned
+ without redaction and may include the supplied values. The tool may submit
+ or perform other side effects. Any item destination
+ restrictions apply to the tool's top-level page and registering frame (if any).
+ """
+
+ bindings: Required[Iterable[VaultWebmcpBindingParam]]
+
+ browser_id: Required[str]
+ """Browser session ID, not a reusable browser name."""
+
+ input: Required[Dict[str, object]]
+ """Public tool arguments with an existing null slot at each binding path.
+
+ At most 64 KiB after JSON serialization, including substituted values. Never
+ include vault values here.
+ """
+
+ page_url: Required[str]
+ """Exact top-level URL from the discovered tool source (fragment omitted).
+
+ This pins the target page; it does not authorize a destination.
+ """
+
+ tool_ref: Required[str]
+ """Opaque reference to the exact live WebMCP registration."""
+
+ type: Required[Literal["webmcp_invoke"]]
+
+ timeout_sec: int
+ """
+ Tool invocation timeout in seconds; preflight and response handling have an
+ additional bounded allowance. An indeterminate outcome is not retried.
+ """
diff --git a/src/kernel/types/vaults/webmcp_invoke_vault_item_operation_result.py b/src/kernel/types/vaults/webmcp_invoke_vault_item_operation_result.py
new file mode 100644
index 00000000..34f85ca4
--- /dev/null
+++ b/src/kernel/types/vaults/webmcp_invoke_vault_item_operation_result.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["WebmcpInvokeVaultItemOperationResult"]
+
+
+class WebmcpInvokeVaultItemOperationResult(BaseModel):
+ """
+ Returns the same tool result fields as the browser WebMCP invoke API, plus the vault operation discriminator. Output and error text are untrusted page-provided data, returned without redaction; tools may include supplied vault values. Inspect the browser page to determine whether the intended site action succeeded.
+ """
+
+ status: Literal["completed", "canceled", "error", "awaiting_submission", "unknown"]
+ """Unknown means invocation may have run; do not retry automatically.
+
+ No status confirms that the website accepted the action.
+ """
+
+ type: Literal["webmcp_invoke"]
+
+ error_text: Optional[str] = None
+ """Untrusted page-provided error text, returned without redaction.
+
+ May contain supplied vault values.
+ """
+
+ invocation_id: Optional[str] = None
+ """Present when the browser reported one."""
+
+ output: Optional[object] = None
+ """Untrusted page-provided output, returned without redaction.
+
+ May contain supplied vault values.
+ """
diff --git a/src/kernel/types/warning.py b/src/kernel/types/warning.py
new file mode 100644
index 00000000..fc138975
--- /dev/null
+++ b/src/kernel/types/warning.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["Warning"]
+
+
+class Warning(BaseModel):
+ code: str
+ """
+ Examples: param_unsupported, preference_unsupported, max_results_clamped,
+ domains_truncated, recency_emulated, filter_emulated, date_filter_overridden,
+ provider_ineligible, fallback_failed, content_partial.
+ """
+
+ message: str
+
+ param: Optional[str] = None
+
+ provider: Optional[str] = None
+
+ result_id: Optional[str] = None
diff --git a/tests/api_resources/audit_logs/__init__.py b/tests/api_resources/audit_logs/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/audit_logs/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/audit_logs/test_export_destinations.py b/tests/api_resources/audit_logs/test_export_destinations.py
new file mode 100644
index 00000000..34b4fde8
--- /dev/null
+++ b/tests/api_resources/audit_logs/test_export_destinations.py
@@ -0,0 +1,592 @@
+# 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.pagination import SyncOffsetPagination, AsyncOffsetPagination
+from kernel.types.audit_logs import (
+ AuditLogExportDestination,
+ AuditLogExportDestinationTestResult,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestExportDestinations:
+ 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:
+ export_destination = client.audit_logs.export_destinations.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Kernel) -> None:
+ export_destination = client.audit_logs.export_destinations.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ kms_key_id="kms_key_id",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Kernel) -> None:
+ response = client.audit_logs.export_destinations.with_raw_response.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Kernel) -> None:
+ with client.audit_logs.export_destinations.with_streaming_response.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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:
+ export_destination = client.audit_logs.export_destinations.retrieve(
+ "id",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.audit_logs.export_destinations.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.audit_logs.export_destinations.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ export_destination = client.audit_logs.export_destinations.update(
+ id="id",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Kernel) -> None:
+ export_destination = client.audit_logs.export_destinations.update(
+ id="id",
+ bucket="xxx",
+ kms_key_id="kms_key_id",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ status="active",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.audit_logs.export_destinations.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.audit_logs.export_destinations.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ export_destination = client.audit_logs.export_destinations.list()
+ assert_matches_type(SyncOffsetPagination[AuditLogExportDestination], export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ export_destination = client.audit_logs.export_destinations.list(
+ limit=1,
+ offset=0,
+ )
+ assert_matches_type(SyncOffsetPagination[AuditLogExportDestination], export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.audit_logs.export_destinations.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = response.parse()
+ assert_matches_type(SyncOffsetPagination[AuditLogExportDestination], export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.audit_logs.export_destinations.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = response.parse()
+ assert_matches_type(SyncOffsetPagination[AuditLogExportDestination], export_destination, 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:
+ export_destination = client.audit_logs.export_destinations.delete(
+ "id",
+ )
+ assert export_destination is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.audit_logs.export_destinations.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = response.parse()
+ assert export_destination is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.audit_logs.export_destinations.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = response.parse()
+ assert export_destination 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.audit_logs.export_destinations.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_test(self, client: Kernel) -> None:
+ export_destination = client.audit_logs.export_destinations.test(
+ "id",
+ )
+ assert_matches_type(AuditLogExportDestinationTestResult, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_test(self, client: Kernel) -> None:
+ response = client.audit_logs.export_destinations.with_raw_response.test(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestinationTestResult, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_test(self, client: Kernel) -> None:
+ with client.audit_logs.export_destinations.with_streaming_response.test(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = response.parse()
+ assert_matches_type(AuditLogExportDestinationTestResult, export_destination, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_test(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.audit_logs.export_destinations.with_raw_response.test(
+ "",
+ )
+
+
+class TestAsyncExportDestinations:
+ 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:
+ export_destination = await async_client.audit_logs.export_destinations.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None:
+ export_destination = await async_client.audit_logs.export_destinations.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ kms_key_id="kms_key_id",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_raw_response.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_streaming_response.create(
+ bucket="xxx",
+ format="jsonl.gz",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ type="s3",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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:
+ export_destination = await async_client.audit_logs.export_destinations.retrieve(
+ "id",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ export_destination = await async_client.audit_logs.export_destinations.update(
+ id="id",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, 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:
+ export_destination = await async_client.audit_logs.export_destinations.update(
+ id="id",
+ bucket="xxx",
+ kms_key_id="kms_key_id",
+ prefix="prefix",
+ region="x",
+ role_arn="x",
+ status="active",
+ )
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestination, export_destination, 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.audit_logs.export_destinations.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:
+ export_destination = await async_client.audit_logs.export_destinations.list()
+ assert_matches_type(AsyncOffsetPagination[AuditLogExportDestination], export_destination, 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:
+ export_destination = await async_client.audit_logs.export_destinations.list(
+ limit=1,
+ offset=0,
+ )
+ assert_matches_type(AsyncOffsetPagination[AuditLogExportDestination], export_destination, 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.audit_logs.export_destinations.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[AuditLogExportDestination], export_destination, 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.audit_logs.export_destinations.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[AuditLogExportDestination], export_destination, 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:
+ export_destination = await async_client.audit_logs.export_destinations.delete(
+ "id",
+ )
+ assert export_destination 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.audit_logs.export_destinations.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = await response.parse()
+ assert export_destination 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.audit_logs.export_destinations.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = await response.parse()
+ assert export_destination 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.audit_logs.export_destinations.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_test(self, async_client: AsyncKernel) -> None:
+ export_destination = await async_client.audit_logs.export_destinations.test(
+ "id",
+ )
+ assert_matches_type(AuditLogExportDestinationTestResult, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_test(self, async_client: AsyncKernel) -> None:
+ response = await async_client.audit_logs.export_destinations.with_raw_response.test(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestinationTestResult, export_destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_test(self, async_client: AsyncKernel) -> None:
+ async with async_client.audit_logs.export_destinations.with_streaming_response.test(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ export_destination = await response.parse()
+ assert_matches_type(AuditLogExportDestinationTestResult, export_destination, 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_test(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.audit_logs.export_destinations.with_raw_response.test(
+ "",
+ )
diff --git a/tests/api_resources/auth/test_connections.py b/tests/api_resources/auth/test_connections.py
index e3da167f..f4bf875f 100644
--- a/tests/api_resources/auth/test_connections.py
+++ b/tests/api_resources/auth/test_connections.py
@@ -14,6 +14,7 @@
ManagedAuth,
LoginResponse,
SubmitFieldsResponse,
+ ManagedAuthTimelineEvent,
)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -38,6 +39,72 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
domain="netflix.com",
profile_name="user-123",
allowed_domains=["login.netflix.com", "auth.netflix.com"],
+ auto_reauth=True,
+ browser={
+ "proxy": {
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
+ "region": "us-east",
+ "stealth": False,
+ "telemetry": {
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ },
+ browser_telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
credential={
"auto": True,
"name": "my-netflix-creds",
@@ -45,6 +112,7 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
"provider": "my-1p",
},
health_check_interval=3600,
+ health_checks=True,
login_url="https://netflix.com/login",
proxy={
"id": "id",
@@ -139,6 +207,72 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
connection = client.auth.connections.update(
id="id",
allowed_domains=["login.netflix.com", "auth.netflix.com"],
+ auto_reauth=True,
+ browser={
+ "proxy": {
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
+ "region": "us-east",
+ "stealth": False,
+ "telemetry": {
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ },
+ browser_telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
credential={
"auto": True,
"name": "my-netflix-creds",
@@ -146,6 +280,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
"provider": "my-1p",
},
health_check_interval=3600,
+ health_checks=True,
login_url="https://netflix.com/login",
proxy={
"id": "id",
@@ -204,6 +339,7 @@ def test_method_list_with_all_params(self, client: Kernel) -> None:
limit=100,
offset=0,
profile_name="profile_name",
+ query="query",
)
assert_matches_type(SyncOffsetPagination[ManagedAuth], connection, path=["response"])
@@ -325,11 +461,77 @@ def test_method_login(self, client: Kernel) -> None:
def test_method_login_with_all_params(self, client: Kernel) -> None:
connection = client.auth.connections.login(
id="id",
+ browser={
+ "proxy": {
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
+ "region": "us-east",
+ "stealth": False,
+ "telemetry": {
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ },
+ browser_telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
proxy={
"id": "id",
"name": "name",
},
record_session=True,
+ skill_mode="enabled",
)
assert_matches_type(LoginResponse, connection, path=["response"])
@@ -380,11 +582,17 @@ def test_method_submit(self, client: Kernel) -> None:
def test_method_submit_with_all_params(self, client: Kernel) -> None:
connection = client.auth.connections.submit(
id="id",
+ field_values={
+ "field_email": "user@example.com",
+ "field_password": "secret",
+ },
fields={
"email": "user@example.com",
"password": "secret",
},
+ interaction_id="mai_abc123xyz",
mfa_option_id="sms",
+ selected_choice_id="google",
sign_in_option_id="work-account",
sso_button_selector="xpath=//button[contains(text(), 'Continue with Google')]",
sso_provider="google",
@@ -425,6 +633,59 @@ def test_path_params_submit(self, client: Kernel) -> None:
id="",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_timeline(self, client: Kernel) -> None:
+ connection = client.auth.connections.timeline(
+ id="id",
+ )
+ assert_matches_type(SyncOffsetPagination[ManagedAuthTimelineEvent], connection, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_timeline_with_all_params(self, client: Kernel) -> None:
+ connection = client.auth.connections.timeline(
+ id="id",
+ limit=100,
+ offset=0,
+ type="login",
+ )
+ assert_matches_type(SyncOffsetPagination[ManagedAuthTimelineEvent], connection, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_timeline(self, client: Kernel) -> None:
+ response = client.auth.connections.with_raw_response.timeline(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connection = response.parse()
+ assert_matches_type(SyncOffsetPagination[ManagedAuthTimelineEvent], connection, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_timeline(self, client: Kernel) -> None:
+ with client.auth.connections.with_streaming_response.timeline(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connection = response.parse()
+ assert_matches_type(SyncOffsetPagination[ManagedAuthTimelineEvent], connection, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_timeline(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.auth.connections.with_raw_response.timeline(
+ id="",
+ )
+
class TestAsyncConnections:
parametrize = pytest.mark.parametrize(
@@ -447,6 +708,72 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
domain="netflix.com",
profile_name="user-123",
allowed_domains=["login.netflix.com", "auth.netflix.com"],
+ auto_reauth=True,
+ browser={
+ "proxy": {
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
+ "region": "us-east",
+ "stealth": False,
+ "telemetry": {
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ },
+ browser_telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
credential={
"auto": True,
"name": "my-netflix-creds",
@@ -454,6 +781,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
"provider": "my-1p",
},
health_check_interval=3600,
+ health_checks=True,
login_url="https://netflix.com/login",
proxy={
"id": "id",
@@ -548,6 +876,72 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
connection = await async_client.auth.connections.update(
id="id",
allowed_domains=["login.netflix.com", "auth.netflix.com"],
+ auto_reauth=True,
+ browser={
+ "proxy": {
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
+ "region": "us-east",
+ "stealth": False,
+ "telemetry": {
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ },
+ browser_telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
credential={
"auto": True,
"name": "my-netflix-creds",
@@ -555,6 +949,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
"provider": "my-1p",
},
health_check_interval=3600,
+ health_checks=True,
login_url="https://netflix.com/login",
proxy={
"id": "id",
@@ -613,6 +1008,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> N
limit=100,
offset=0,
profile_name="profile_name",
+ query="query",
)
assert_matches_type(AsyncOffsetPagination[ManagedAuth], connection, path=["response"])
@@ -734,11 +1130,77 @@ async def test_method_login(self, async_client: AsyncKernel) -> None:
async def test_method_login_with_all_params(self, async_client: AsyncKernel) -> None:
connection = await async_client.auth.connections.login(
id="id",
+ browser={
+ "proxy": {
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
+ "region": "us-east",
+ "stealth": False,
+ "telemetry": {
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ },
+ browser_telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
proxy={
"id": "id",
"name": "name",
},
record_session=True,
+ skill_mode="enabled",
)
assert_matches_type(LoginResponse, connection, path=["response"])
@@ -789,11 +1251,17 @@ async def test_method_submit(self, async_client: AsyncKernel) -> None:
async def test_method_submit_with_all_params(self, async_client: AsyncKernel) -> None:
connection = await async_client.auth.connections.submit(
id="id",
+ field_values={
+ "field_email": "user@example.com",
+ "field_password": "secret",
+ },
fields={
"email": "user@example.com",
"password": "secret",
},
+ interaction_id="mai_abc123xyz",
mfa_option_id="sms",
+ selected_choice_id="google",
sign_in_option_id="work-account",
sso_button_selector="xpath=//button[contains(text(), 'Continue with Google')]",
sso_provider="google",
@@ -833,3 +1301,56 @@ async def test_path_params_submit(self, async_client: AsyncKernel) -> None:
await async_client.auth.connections.with_raw_response.submit(
id="",
)
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_timeline(self, async_client: AsyncKernel) -> None:
+ connection = await async_client.auth.connections.timeline(
+ id="id",
+ )
+ assert_matches_type(AsyncOffsetPagination[ManagedAuthTimelineEvent], connection, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_timeline_with_all_params(self, async_client: AsyncKernel) -> None:
+ connection = await async_client.auth.connections.timeline(
+ id="id",
+ limit=100,
+ offset=0,
+ type="login",
+ )
+ assert_matches_type(AsyncOffsetPagination[ManagedAuthTimelineEvent], connection, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_timeline(self, async_client: AsyncKernel) -> None:
+ response = await async_client.auth.connections.with_raw_response.timeline(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connection = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[ManagedAuthTimelineEvent], connection, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_timeline(self, async_client: AsyncKernel) -> None:
+ async with async_client.auth.connections.with_streaming_response.timeline(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connection = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[ManagedAuthTimelineEvent], connection, 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_timeline(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.auth.connections.with_raw_response.timeline(
+ id="",
+ )
diff --git a/tests/api_resources/auth/test_context.py b/tests/api_resources/auth/test_context.py
new file mode 100644
index 00000000..18ed5c58
--- /dev/null
+++ b/tests/api_resources/auth/test_context.py
@@ -0,0 +1,80 @@
+# 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.auth import AuthContext
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestContext:
+ 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:
+ context = client.auth.context.retrieve()
+ assert_matches_type(AuthContext, context, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.auth.context.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context = response.parse()
+ assert_matches_type(AuthContext, context, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.auth.context.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context = response.parse()
+ assert_matches_type(AuthContext, context, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncContext:
+ 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:
+ context = await async_client.auth.context.retrieve()
+ assert_matches_type(AuthContext, context, 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.auth.context.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context = await response.parse()
+ assert_matches_type(AuthContext, context, 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.auth.context.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context = await response.parse()
+ assert_matches_type(AuthContext, context, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/browsers/fs/test_watch.py b/tests/api_resources/browsers/fs/test_watch.py
index b28086ba..e81b2ade 100644
--- a/tests/api_resources/browsers/fs/test_watch.py
+++ b/tests/api_resources/browsers/fs/test_watch.py
@@ -22,7 +22,7 @@ class TestWatch:
def test_method_events(self, client: Kernel) -> None:
watch_stream = client.browsers.fs.watch.events(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
watch_stream.response.close()
@@ -31,7 +31,7 @@ def test_method_events(self, client: Kernel) -> None:
def test_raw_response_events(self, client: Kernel) -> None:
response = client.browsers.fs.watch.with_raw_response.events(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -43,7 +43,7 @@ def test_raw_response_events(self, client: Kernel) -> None:
def test_streaming_response_events(self, client: Kernel) -> None:
with client.browsers.fs.watch.with_streaming_response.events(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -56,23 +56,23 @@ def test_streaming_response_events(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_events(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.browsers.fs.watch.with_raw_response.events(
watch_id="watch_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `watch_id` but received ''"):
client.browsers.fs.watch.with_raw_response.events(
watch_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_start(self, client: Kernel) -> None:
watch = client.browsers.fs.watch.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
)
assert_matches_type(WatchStartResponse, watch, path=["response"])
@@ -81,7 +81,7 @@ def test_method_start(self, client: Kernel) -> None:
@parametrize
def test_method_start_with_all_params(self, client: Kernel) -> None:
watch = client.browsers.fs.watch.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
recursive=True,
)
@@ -91,7 +91,7 @@ def test_method_start_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_start(self, client: Kernel) -> None:
response = client.browsers.fs.watch.with_raw_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
)
@@ -104,7 +104,7 @@ def test_raw_response_start(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_start(self, client: Kernel) -> None:
with client.browsers.fs.watch.with_streaming_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
) as response:
assert not response.is_closed
@@ -118,9 +118,9 @@ def test_streaming_response_start(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_start(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.browsers.fs.watch.with_raw_response.start(
- id="",
+ id_or_name="",
path="path",
)
@@ -129,7 +129,7 @@ def test_path_params_start(self, client: Kernel) -> None:
def test_method_stop(self, client: Kernel) -> None:
watch = client.browsers.fs.watch.stop(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert watch is None
@@ -138,7 +138,7 @@ def test_method_stop(self, client: Kernel) -> None:
def test_raw_response_stop(self, client: Kernel) -> None:
response = client.browsers.fs.watch.with_raw_response.stop(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -151,7 +151,7 @@ def test_raw_response_stop(self, client: Kernel) -> None:
def test_streaming_response_stop(self, client: Kernel) -> None:
with client.browsers.fs.watch.with_streaming_response.stop(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -164,16 +164,16 @@ def test_streaming_response_stop(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_stop(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.browsers.fs.watch.with_raw_response.stop(
watch_id="watch_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `watch_id` but received ''"):
client.browsers.fs.watch.with_raw_response.stop(
watch_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@@ -187,7 +187,7 @@ class TestAsyncWatch:
async def test_method_events(self, async_client: AsyncKernel) -> None:
watch_stream = await async_client.browsers.fs.watch.events(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
await watch_stream.response.aclose()
@@ -196,7 +196,7 @@ async def test_method_events(self, async_client: AsyncKernel) -> None:
async def test_raw_response_events(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.watch.with_raw_response.events(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -208,7 +208,7 @@ async def test_raw_response_events(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_events(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.watch.with_streaming_response.events(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -221,23 +221,23 @@ async def test_streaming_response_events(self, async_client: AsyncKernel) -> Non
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_events(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.browsers.fs.watch.with_raw_response.events(
watch_id="watch_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `watch_id` but received ''"):
await async_client.browsers.fs.watch.with_raw_response.events(
watch_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_start(self, async_client: AsyncKernel) -> None:
watch = await async_client.browsers.fs.watch.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
)
assert_matches_type(WatchStartResponse, watch, path=["response"])
@@ -246,7 +246,7 @@ async def test_method_start(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_start_with_all_params(self, async_client: AsyncKernel) -> None:
watch = await async_client.browsers.fs.watch.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
recursive=True,
)
@@ -256,7 +256,7 @@ async def test_method_start_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_start(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.watch.with_raw_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
)
@@ -269,7 +269,7 @@ async def test_raw_response_start(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_start(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.watch.with_streaming_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="path",
) as response:
assert not response.is_closed
@@ -283,9 +283,9 @@ async def test_streaming_response_start(self, async_client: AsyncKernel) -> None
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_start(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.browsers.fs.watch.with_raw_response.start(
- id="",
+ id_or_name="",
path="path",
)
@@ -294,7 +294,7 @@ async def test_path_params_start(self, async_client: AsyncKernel) -> None:
async def test_method_stop(self, async_client: AsyncKernel) -> None:
watch = await async_client.browsers.fs.watch.stop(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert watch is None
@@ -303,7 +303,7 @@ async def test_method_stop(self, async_client: AsyncKernel) -> None:
async def test_raw_response_stop(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.watch.with_raw_response.stop(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -316,7 +316,7 @@ async def test_raw_response_stop(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_stop(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.watch.with_streaming_response.stop(
watch_id="watch_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -329,14 +329,14 @@ async def test_streaming_response_stop(self, async_client: AsyncKernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_stop(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.browsers.fs.watch.with_raw_response.stop(
watch_id="watch_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `watch_id` but received ''"):
await async_client.browsers.fs.watch.with_raw_response.stop(
watch_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
diff --git a/tests/api_resources/browsers/playwright/__init__.py b/tests/api_resources/browsers/playwright/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/browsers/playwright/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/browsers/playwright/test_executors.py b/tests/api_resources/browsers/playwright/test_executors.py
new file mode 100644
index 00000000..4b3b564e
--- /dev/null
+++ b/tests/api_resources/browsers/playwright/test_executors.py
@@ -0,0 +1,232 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from kernel import Kernel, AsyncKernel
+from tests.utils import assert_matches_type
+from kernel.types.browsers.playwright import ExecutorList
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestExecutors:
+ 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_list(self, client: Kernel) -> None:
+ executor = client.browsers.playwright.executors.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.browsers.playwright.executors.with_raw_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = response.parse()
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.browsers.playwright.executors.with_streaming_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = response.parse()
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.playwright.executors.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Kernel) -> None:
+ executor = client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete_with_all_params(self, client: Kernel) -> None:
+ executor = client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ close_tab=True,
+ )
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = response.parse()
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.browsers.playwright.executors.with_streaming_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = response.parse()
+ assert executor 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_or_name` but received ''"):
+ client.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
+ client.browsers.playwright.executors.with_raw_response.delete(
+ name="",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+
+class TestAsyncExecutors:
+ 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_list(self, async_client: AsyncKernel) -> None:
+ executor = await async_client.browsers.playwright.executors.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(ExecutorList, executor, 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.browsers.playwright.executors.with_raw_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = await response.parse()
+ assert_matches_type(ExecutorList, executor, 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.browsers.playwright.executors.with_streaming_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = await response.parse()
+ assert_matches_type(ExecutorList, executor, 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_list(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.playwright.executors.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncKernel) -> None:
+ executor = await async_client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete_with_all_params(self, async_client: AsyncKernel) -> None:
+ executor = await async_client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ close_tab=True,
+ )
+ assert executor 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.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = await response.parse()
+ assert executor 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.browsers.playwright.executors.with_streaming_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = await response.parse()
+ assert executor 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_or_name` but received ''"):
+ await async_client.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
+ await async_client.browsers.playwright.executors.with_raw_response.delete(
+ name="",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
diff --git a/tests/api_resources/browsers/test_computer.py b/tests/api_resources/browsers/test_computer.py
index 31974d5b..916dbcb0 100644
--- a/tests/api_resources/browsers/test_computer.py
+++ b/tests/api_resources/browsers/test_computer.py
@@ -33,7 +33,7 @@ class TestComputer:
@parametrize
def test_method_batch(self, client: Kernel) -> None:
computer = client.browsers.computer.batch(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
actions=[{"type": "click_mouse"}],
)
assert computer is None
@@ -42,7 +42,7 @@ def test_method_batch(self, client: Kernel) -> None:
@parametrize
def test_raw_response_batch(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.batch(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
actions=[{"type": "click_mouse"}],
)
@@ -55,7 +55,7 @@ def test_raw_response_batch(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_batch(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.batch(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
actions=[{"type": "click_mouse"}],
) as response:
assert not response.is_closed
@@ -69,18 +69,20 @@ def test_streaming_response_batch(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_batch(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.browsers.computer.with_raw_response.batch(
- id="",
+ id_or_name="",
actions=[{"type": "click_mouse"}],
)
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_method_capture_screenshot(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
computer = client.browsers.computer.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert computer.is_closed
assert computer.json() == {"foo": "bar"}
@@ -90,9 +92,11 @@ def test_method_capture_screenshot(self, client: Kernel, respx_mock: MockRouter)
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_method_capture_screenshot_with_all_params(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
computer = client.browsers.computer.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
region={
"height": 0,
"width": 0,
@@ -108,10 +112,12 @@ def test_method_capture_screenshot_with_all_params(self, client: Kernel, respx_m
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_raw_response_capture_screenshot(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
computer = client.browsers.computer.with_raw_response.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert computer.is_closed is True
@@ -122,9 +128,11 @@ def test_raw_response_capture_screenshot(self, client: Kernel, respx_mock: MockR
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_streaming_response_capture_screenshot(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
with client.browsers.computer.with_streaming_response.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as computer:
assert not computer.is_closed
assert computer.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -138,16 +146,16 @@ def test_streaming_response_capture_screenshot(self, client: Kernel, respx_mock:
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_path_params_capture_screenshot(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.browsers.computer.with_raw_response.capture_screenshot(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_click_mouse(self, client: Kernel) -> None:
computer = client.browsers.computer.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -157,7 +165,7 @@ def test_method_click_mouse(self, client: Kernel) -> None:
@parametrize
def test_method_click_mouse_with_all_params(self, client: Kernel) -> None:
computer = client.browsers.computer.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
button="left",
@@ -171,7 +179,7 @@ def test_method_click_mouse_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_click_mouse(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -185,7 +193,7 @@ def test_raw_response_click_mouse(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_click_mouse(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
) as response:
@@ -200,9 +208,9 @@ def test_streaming_response_click_mouse(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_click_mouse(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.browsers.computer.with_raw_response.click_mouse(
- id="",
+ id_or_name="",
x=0,
y=0,
)
@@ -211,7 +219,7 @@ def test_path_params_click_mouse(self, client: Kernel) -> None:
@parametrize
def test_method_drag_mouse(self, client: Kernel) -> None:
computer = client.browsers.computer.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
)
assert computer is None
@@ -220,7 +228,7 @@ def test_method_drag_mouse(self, client: Kernel) -> None:
@parametrize
def test_method_drag_mouse_with_all_params(self, client: Kernel) -> None:
computer = client.browsers.computer.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
button="left",
delay=0,
@@ -236,7 +244,7 @@ def test_method_drag_mouse_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_drag_mouse(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
)
@@ -249,7 +257,7 @@ def test_raw_response_drag_mouse(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_drag_mouse(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
) as response:
assert not response.is_closed
@@ -263,9 +271,9 @@ def test_streaming_response_drag_mouse(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_drag_mouse(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.browsers.computer.with_raw_response.drag_mouse(
- id="",
+ id_or_name="",
path=[[0, 0], [0, 0]],
)
@@ -273,7 +281,7 @@ def test_path_params_drag_mouse(self, client: Kernel) -> None:
@parametrize
def test_method_get_mouse_position(self, client: Kernel) -> None:
computer = client.browsers.computer.get_mouse_position(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ComputerGetMousePositionResponse, computer, path=["response"])
@@ -281,7 +289,7 @@ def test_method_get_mouse_position(self, client: Kernel) -> None:
@parametrize
def test_raw_response_get_mouse_position(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.get_mouse_position(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -293,7 +301,7 @@ def test_raw_response_get_mouse_position(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_get_mouse_position(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.get_mouse_position(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -306,7 +314,7 @@ def test_streaming_response_get_mouse_position(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_get_mouse_position(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.browsers.computer.with_raw_response.get_mouse_position(
"",
)
@@ -315,7 +323,7 @@ def test_path_params_get_mouse_position(self, client: Kernel) -> None:
@parametrize
def test_method_move_mouse(self, client: Kernel) -> None:
computer = client.browsers.computer.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -325,7 +333,7 @@ def test_method_move_mouse(self, client: Kernel) -> None:
@parametrize
def test_method_move_mouse_with_all_params(self, client: Kernel) -> None:
computer = client.browsers.computer.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
duration_ms=50,
@@ -338,7 +346,7 @@ def test_method_move_mouse_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_move_mouse(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -352,7 +360,7 @@ def test_raw_response_move_mouse(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_move_mouse(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
) as response:
@@ -367,9 +375,9 @@ def test_streaming_response_move_mouse(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_move_mouse(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.browsers.computer.with_raw_response.move_mouse(
- id="",
+ id_or_name="",
x=0,
y=0,
)
@@ -378,7 +386,7 @@ def test_path_params_move_mouse(self, client: Kernel) -> None:
@parametrize
def test_method_press_key(self, client: Kernel) -> None:
computer = client.browsers.computer.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
)
assert computer is None
@@ -387,7 +395,7 @@ def test_method_press_key(self, client: Kernel) -> None:
@parametrize
def test_method_press_key_with_all_params(self, client: Kernel) -> None:
computer = client.browsers.computer.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
duration=0,
hold_keys=["string"],
@@ -398,7 +406,7 @@ def test_method_press_key_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_press_key(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
)
@@ -411,7 +419,7 @@ def test_raw_response_press_key(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_press_key(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
) as response:
assert not response.is_closed
@@ -425,9 +433,9 @@ def test_streaming_response_press_key(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_press_key(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.browsers.computer.with_raw_response.press_key(
- id="",
+ id_or_name="",
keys=["string"],
)
@@ -435,7 +443,7 @@ def test_path_params_press_key(self, client: Kernel) -> None:
@parametrize
def test_method_read_clipboard(self, client: Kernel) -> None:
computer = client.browsers.computer.read_clipboard(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ComputerReadClipboardResponse, computer, path=["response"])
@@ -443,7 +451,7 @@ def test_method_read_clipboard(self, client: Kernel) -> None:
@parametrize
def test_raw_response_read_clipboard(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.read_clipboard(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -455,7 +463,7 @@ def test_raw_response_read_clipboard(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_read_clipboard(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.read_clipboard(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -468,7 +476,7 @@ def test_streaming_response_read_clipboard(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_read_clipboard(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.browsers.computer.with_raw_response.read_clipboard(
"",
)
@@ -477,7 +485,7 @@ def test_path_params_read_clipboard(self, client: Kernel) -> None:
@parametrize
def test_method_scroll(self, client: Kernel) -> None:
computer = client.browsers.computer.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -487,7 +495,7 @@ def test_method_scroll(self, client: Kernel) -> None:
@parametrize
def test_method_scroll_with_all_params(self, client: Kernel) -> None:
computer = client.browsers.computer.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
delta_x=0,
@@ -500,7 +508,7 @@ def test_method_scroll_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_scroll(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -514,7 +522,7 @@ def test_raw_response_scroll(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_scroll(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
) as response:
@@ -529,9 +537,9 @@ def test_streaming_response_scroll(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_scroll(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.browsers.computer.with_raw_response.scroll(
- id="",
+ id_or_name="",
x=0,
y=0,
)
@@ -540,7 +548,7 @@ def test_path_params_scroll(self, client: Kernel) -> None:
@parametrize
def test_method_set_cursor_visibility(self, client: Kernel) -> None:
computer = client.browsers.computer.set_cursor_visibility(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
hidden=True,
)
assert_matches_type(ComputerSetCursorVisibilityResponse, computer, path=["response"])
@@ -549,7 +557,7 @@ def test_method_set_cursor_visibility(self, client: Kernel) -> None:
@parametrize
def test_raw_response_set_cursor_visibility(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.set_cursor_visibility(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
hidden=True,
)
@@ -562,7 +570,7 @@ def test_raw_response_set_cursor_visibility(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_set_cursor_visibility(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.set_cursor_visibility(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
hidden=True,
) as response:
assert not response.is_closed
@@ -576,9 +584,9 @@ def test_streaming_response_set_cursor_visibility(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_set_cursor_visibility(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.browsers.computer.with_raw_response.set_cursor_visibility(
- id="",
+ id_or_name="",
hidden=True,
)
@@ -586,7 +594,7 @@ def test_path_params_set_cursor_visibility(self, client: Kernel) -> None:
@parametrize
def test_method_type_text(self, client: Kernel) -> None:
computer = client.browsers.computer.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
assert computer is None
@@ -595,7 +603,7 @@ def test_method_type_text(self, client: Kernel) -> None:
@parametrize
def test_method_type_text_with_all_params(self, client: Kernel) -> None:
computer = client.browsers.computer.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
delay=0,
)
@@ -605,7 +613,7 @@ def test_method_type_text_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_type_text(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
@@ -618,7 +626,7 @@ def test_raw_response_type_text(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_type_text(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
) as response:
assert not response.is_closed
@@ -632,9 +640,9 @@ def test_streaming_response_type_text(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_type_text(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.browsers.computer.with_raw_response.type_text(
- id="",
+ id_or_name="",
text="text",
)
@@ -642,7 +650,7 @@ def test_path_params_type_text(self, client: Kernel) -> None:
@parametrize
def test_method_write_clipboard(self, client: Kernel) -> None:
computer = client.browsers.computer.write_clipboard(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
assert computer is None
@@ -651,7 +659,7 @@ def test_method_write_clipboard(self, client: Kernel) -> None:
@parametrize
def test_raw_response_write_clipboard(self, client: Kernel) -> None:
response = client.browsers.computer.with_raw_response.write_clipboard(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
@@ -664,7 +672,7 @@ def test_raw_response_write_clipboard(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_write_clipboard(self, client: Kernel) -> None:
with client.browsers.computer.with_streaming_response.write_clipboard(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
) as response:
assert not response.is_closed
@@ -678,9 +686,9 @@ def test_streaming_response_write_clipboard(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_write_clipboard(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.browsers.computer.with_raw_response.write_clipboard(
- id="",
+ id_or_name="",
text="text",
)
@@ -694,7 +702,7 @@ class TestAsyncComputer:
@parametrize
async def test_method_batch(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.batch(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
actions=[{"type": "click_mouse"}],
)
assert computer is None
@@ -703,7 +711,7 @@ async def test_method_batch(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_batch(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.batch(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
actions=[{"type": "click_mouse"}],
)
@@ -716,7 +724,7 @@ async def test_raw_response_batch(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_batch(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.batch(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
actions=[{"type": "click_mouse"}],
) as response:
assert not response.is_closed
@@ -730,18 +738,20 @@ async def test_streaming_response_batch(self, async_client: AsyncKernel) -> None
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_batch(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.browsers.computer.with_raw_response.batch(
- id="",
+ id_or_name="",
actions=[{"type": "click_mouse"}],
)
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_method_capture_screenshot(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
computer = await async_client.browsers.computer.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert computer.is_closed
assert await computer.json() == {"foo": "bar"}
@@ -753,9 +763,11 @@ async def test_method_capture_screenshot(self, async_client: AsyncKernel, respx_
async def test_method_capture_screenshot_with_all_params(
self, async_client: AsyncKernel, respx_mock: MockRouter
) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
computer = await async_client.browsers.computer.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
region={
"height": 0,
"width": 0,
@@ -771,10 +783,12 @@ async def test_method_capture_screenshot_with_all_params(
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_raw_response_capture_screenshot(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
computer = await async_client.browsers.computer.with_raw_response.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert computer.is_closed is True
@@ -787,9 +801,11 @@ async def test_raw_response_capture_screenshot(self, async_client: AsyncKernel,
async def test_streaming_response_capture_screenshot(
self, async_client: AsyncKernel, respx_mock: MockRouter
) -> None:
- respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.post("/browsers/htzv5orfit78e1m2biiifpbv/computer/screenshot").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
async with async_client.browsers.computer.with_streaming_response.capture_screenshot(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as computer:
assert not computer.is_closed
assert computer.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -803,16 +819,16 @@ async def test_streaming_response_capture_screenshot(
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_path_params_capture_screenshot(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.browsers.computer.with_raw_response.capture_screenshot(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_click_mouse(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -822,7 +838,7 @@ async def test_method_click_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_click_mouse_with_all_params(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
button="left",
@@ -836,7 +852,7 @@ async def test_method_click_mouse_with_all_params(self, async_client: AsyncKerne
@parametrize
async def test_raw_response_click_mouse(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -850,7 +866,7 @@ async def test_raw_response_click_mouse(self, async_client: AsyncKernel) -> None
@parametrize
async def test_streaming_response_click_mouse(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.click_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
) as response:
@@ -865,9 +881,9 @@ async def test_streaming_response_click_mouse(self, async_client: AsyncKernel) -
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_click_mouse(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.browsers.computer.with_raw_response.click_mouse(
- id="",
+ id_or_name="",
x=0,
y=0,
)
@@ -876,7 +892,7 @@ async def test_path_params_click_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_drag_mouse(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
)
assert computer is None
@@ -885,7 +901,7 @@ async def test_method_drag_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_drag_mouse_with_all_params(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
button="left",
delay=0,
@@ -901,7 +917,7 @@ async def test_method_drag_mouse_with_all_params(self, async_client: AsyncKernel
@parametrize
async def test_raw_response_drag_mouse(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
)
@@ -914,7 +930,7 @@ async def test_raw_response_drag_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_drag_mouse(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.drag_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path=[[0, 0], [0, 0]],
) as response:
assert not response.is_closed
@@ -928,9 +944,9 @@ async def test_streaming_response_drag_mouse(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_drag_mouse(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.browsers.computer.with_raw_response.drag_mouse(
- id="",
+ id_or_name="",
path=[[0, 0], [0, 0]],
)
@@ -938,7 +954,7 @@ async def test_path_params_drag_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_get_mouse_position(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.get_mouse_position(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ComputerGetMousePositionResponse, computer, path=["response"])
@@ -946,7 +962,7 @@ async def test_method_get_mouse_position(self, async_client: AsyncKernel) -> Non
@parametrize
async def test_raw_response_get_mouse_position(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.get_mouse_position(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -958,7 +974,7 @@ async def test_raw_response_get_mouse_position(self, async_client: AsyncKernel)
@parametrize
async def test_streaming_response_get_mouse_position(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.get_mouse_position(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -971,7 +987,7 @@ async def test_streaming_response_get_mouse_position(self, async_client: AsyncKe
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_get_mouse_position(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.browsers.computer.with_raw_response.get_mouse_position(
"",
)
@@ -980,7 +996,7 @@ async def test_path_params_get_mouse_position(self, async_client: AsyncKernel) -
@parametrize
async def test_method_move_mouse(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -990,7 +1006,7 @@ async def test_method_move_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_move_mouse_with_all_params(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
duration_ms=50,
@@ -1003,7 +1019,7 @@ async def test_method_move_mouse_with_all_params(self, async_client: AsyncKernel
@parametrize
async def test_raw_response_move_mouse(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -1017,7 +1033,7 @@ async def test_raw_response_move_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_move_mouse(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.move_mouse(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
) as response:
@@ -1032,9 +1048,9 @@ async def test_streaming_response_move_mouse(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_move_mouse(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.browsers.computer.with_raw_response.move_mouse(
- id="",
+ id_or_name="",
x=0,
y=0,
)
@@ -1043,7 +1059,7 @@ async def test_path_params_move_mouse(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_press_key(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
)
assert computer is None
@@ -1052,7 +1068,7 @@ async def test_method_press_key(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_press_key_with_all_params(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
duration=0,
hold_keys=["string"],
@@ -1063,7 +1079,7 @@ async def test_method_press_key_with_all_params(self, async_client: AsyncKernel)
@parametrize
async def test_raw_response_press_key(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
)
@@ -1076,7 +1092,7 @@ async def test_raw_response_press_key(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_press_key(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.press_key(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
keys=["string"],
) as response:
assert not response.is_closed
@@ -1090,9 +1106,9 @@ async def test_streaming_response_press_key(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_press_key(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.browsers.computer.with_raw_response.press_key(
- id="",
+ id_or_name="",
keys=["string"],
)
@@ -1100,7 +1116,7 @@ async def test_path_params_press_key(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_read_clipboard(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.read_clipboard(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ComputerReadClipboardResponse, computer, path=["response"])
@@ -1108,7 +1124,7 @@ async def test_method_read_clipboard(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_read_clipboard(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.read_clipboard(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -1120,7 +1136,7 @@ async def test_raw_response_read_clipboard(self, async_client: AsyncKernel) -> N
@parametrize
async def test_streaming_response_read_clipboard(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.read_clipboard(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1133,7 +1149,7 @@ async def test_streaming_response_read_clipboard(self, async_client: AsyncKernel
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_read_clipboard(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.browsers.computer.with_raw_response.read_clipboard(
"",
)
@@ -1142,7 +1158,7 @@ async def test_path_params_read_clipboard(self, async_client: AsyncKernel) -> No
@parametrize
async def test_method_scroll(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -1152,7 +1168,7 @@ async def test_method_scroll(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_scroll_with_all_params(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
delta_x=0,
@@ -1165,7 +1181,7 @@ async def test_method_scroll_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_scroll(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
)
@@ -1179,7 +1195,7 @@ async def test_raw_response_scroll(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_scroll(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.scroll(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
x=0,
y=0,
) as response:
@@ -1194,9 +1210,9 @@ async def test_streaming_response_scroll(self, async_client: AsyncKernel) -> Non
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_scroll(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.browsers.computer.with_raw_response.scroll(
- id="",
+ id_or_name="",
x=0,
y=0,
)
@@ -1205,7 +1221,7 @@ async def test_path_params_scroll(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_set_cursor_visibility(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.set_cursor_visibility(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
hidden=True,
)
assert_matches_type(ComputerSetCursorVisibilityResponse, computer, path=["response"])
@@ -1214,7 +1230,7 @@ async def test_method_set_cursor_visibility(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_set_cursor_visibility(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.set_cursor_visibility(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
hidden=True,
)
@@ -1227,7 +1243,7 @@ async def test_raw_response_set_cursor_visibility(self, async_client: AsyncKerne
@parametrize
async def test_streaming_response_set_cursor_visibility(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.set_cursor_visibility(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
hidden=True,
) as response:
assert not response.is_closed
@@ -1241,9 +1257,9 @@ async def test_streaming_response_set_cursor_visibility(self, async_client: Asyn
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_set_cursor_visibility(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.browsers.computer.with_raw_response.set_cursor_visibility(
- id="",
+ id_or_name="",
hidden=True,
)
@@ -1251,7 +1267,7 @@ async def test_path_params_set_cursor_visibility(self, async_client: AsyncKernel
@parametrize
async def test_method_type_text(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
assert computer is None
@@ -1260,7 +1276,7 @@ async def test_method_type_text(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_type_text_with_all_params(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
delay=0,
)
@@ -1270,7 +1286,7 @@ async def test_method_type_text_with_all_params(self, async_client: AsyncKernel)
@parametrize
async def test_raw_response_type_text(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
@@ -1283,7 +1299,7 @@ async def test_raw_response_type_text(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_type_text(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.type_text(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
) as response:
assert not response.is_closed
@@ -1297,9 +1313,9 @@ async def test_streaming_response_type_text(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_type_text(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.browsers.computer.with_raw_response.type_text(
- id="",
+ id_or_name="",
text="text",
)
@@ -1307,7 +1323,7 @@ async def test_path_params_type_text(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_write_clipboard(self, async_client: AsyncKernel) -> None:
computer = await async_client.browsers.computer.write_clipboard(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
assert computer is None
@@ -1316,7 +1332,7 @@ async def test_method_write_clipboard(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_write_clipboard(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.computer.with_raw_response.write_clipboard(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
)
@@ -1329,7 +1345,7 @@ async def test_raw_response_write_clipboard(self, async_client: AsyncKernel) ->
@parametrize
async def test_streaming_response_write_clipboard(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.computer.with_streaming_response.write_clipboard(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
text="text",
) as response:
assert not response.is_closed
@@ -1343,8 +1359,8 @@ async def test_streaming_response_write_clipboard(self, async_client: AsyncKerne
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_write_clipboard(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.browsers.computer.with_raw_response.write_clipboard(
- id="",
+ id_or_name="",
text="text",
)
diff --git a/tests/api_resources/browsers/test_fs.py b/tests/api_resources/browsers/test_fs.py
index ceb179a0..f980777a 100644
--- a/tests/api_resources/browsers/test_fs.py
+++ b/tests/api_resources/browsers/test_fs.py
@@ -32,7 +32,7 @@ class TestFs:
@parametrize
def test_method_create_directory(self, client: Kernel) -> None:
f = client.browsers.fs.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f is None
@@ -41,7 +41,7 @@ def test_method_create_directory(self, client: Kernel) -> None:
@parametrize
def test_method_create_directory_with_all_params(self, client: Kernel) -> None:
f = client.browsers.fs.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
mode="0611",
)
@@ -51,7 +51,7 @@ def test_method_create_directory_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_create_directory(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -64,7 +64,7 @@ def test_raw_response_create_directory(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_create_directory(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -78,9 +78,9 @@ def test_streaming_response_create_directory(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_create_directory(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.browsers.fs.with_raw_response.create_directory(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -88,7 +88,7 @@ def test_path_params_create_directory(self, client: Kernel) -> None:
@parametrize
def test_method_delete_directory(self, client: Kernel) -> None:
f = client.browsers.fs.delete_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f is None
@@ -97,7 +97,7 @@ def test_method_delete_directory(self, client: Kernel) -> None:
@parametrize
def test_raw_response_delete_directory(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.delete_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -110,7 +110,7 @@ def test_raw_response_delete_directory(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_delete_directory(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.delete_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -124,9 +124,9 @@ def test_streaming_response_delete_directory(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_delete_directory(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.browsers.fs.with_raw_response.delete_directory(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -134,7 +134,7 @@ def test_path_params_delete_directory(self, client: Kernel) -> None:
@parametrize
def test_method_delete_file(self, client: Kernel) -> None:
f = client.browsers.fs.delete_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f is None
@@ -143,7 +143,7 @@ def test_method_delete_file(self, client: Kernel) -> None:
@parametrize
def test_raw_response_delete_file(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.delete_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -156,7 +156,7 @@ def test_raw_response_delete_file(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_delete_file(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.delete_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -170,18 +170,20 @@ def test_streaming_response_delete_file(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_delete_file(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.browsers.fs.with_raw_response.delete_file(
- id="",
+ id_or_name="",
path="/J!",
)
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_method_download_dir_zip(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/download_dir_zip").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = client.browsers.fs.download_dir_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f.is_closed
@@ -192,10 +194,12 @@ def test_method_download_dir_zip(self, client: Kernel, respx_mock: MockRouter) -
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_raw_response_download_dir_zip(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/download_dir_zip").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = client.browsers.fs.with_raw_response.download_dir_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -207,9 +211,11 @@ def test_raw_response_download_dir_zip(self, client: Kernel, respx_mock: MockRou
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_streaming_response_download_dir_zip(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/download_dir_zip").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
with client.browsers.fs.with_streaming_response.download_dir_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as f:
assert not f.is_closed
@@ -224,9 +230,9 @@ def test_streaming_response_download_dir_zip(self, client: Kernel, respx_mock: M
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_path_params_download_dir_zip(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.fs.with_raw_response.download_dir_zip(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -234,7 +240,7 @@ def test_path_params_download_dir_zip(self, client: Kernel) -> None:
@parametrize
def test_method_file_info(self, client: Kernel) -> None:
f = client.browsers.fs.file_info(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert_matches_type(FFileInfoResponse, f, path=["response"])
@@ -243,7 +249,7 @@ def test_method_file_info(self, client: Kernel) -> None:
@parametrize
def test_raw_response_file_info(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.file_info(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -256,7 +262,7 @@ def test_raw_response_file_info(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_file_info(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.file_info(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -270,9 +276,9 @@ def test_streaming_response_file_info(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_file_info(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.browsers.fs.with_raw_response.file_info(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -280,7 +286,7 @@ def test_path_params_file_info(self, client: Kernel) -> None:
@parametrize
def test_method_list_files(self, client: Kernel) -> None:
f = client.browsers.fs.list_files(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert_matches_type(FListFilesResponse, f, path=["response"])
@@ -289,7 +295,7 @@ def test_method_list_files(self, client: Kernel) -> None:
@parametrize
def test_raw_response_list_files(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.list_files(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -302,7 +308,7 @@ def test_raw_response_list_files(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_list_files(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.list_files(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -316,9 +322,9 @@ def test_streaming_response_list_files(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_list_files(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.browsers.fs.with_raw_response.list_files(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -326,7 +332,7 @@ def test_path_params_list_files(self, client: Kernel) -> None:
@parametrize
def test_method_move(self, client: Kernel) -> None:
f = client.browsers.fs.move(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
src_path="/J!",
)
@@ -336,7 +342,7 @@ def test_method_move(self, client: Kernel) -> None:
@parametrize
def test_raw_response_move(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.move(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
src_path="/J!",
)
@@ -350,7 +356,7 @@ def test_raw_response_move(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_move(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.move(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
src_path="/J!",
) as response:
@@ -365,9 +371,9 @@ def test_streaming_response_move(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_move(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.browsers.fs.with_raw_response.move(
- id="",
+ id_or_name="",
dest_path="/J!",
src_path="/J!",
)
@@ -375,9 +381,11 @@ def test_path_params_move(self, client: Kernel) -> None:
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_method_read_file(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/read_file").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/read_file").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = client.browsers.fs.read_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f.is_closed
@@ -388,10 +396,12 @@ def test_method_read_file(self, client: Kernel, respx_mock: MockRouter) -> None:
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_raw_response_read_file(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/read_file").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/read_file").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = client.browsers.fs.with_raw_response.read_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -403,9 +413,11 @@ def test_raw_response_read_file(self, client: Kernel, respx_mock: MockRouter) ->
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_streaming_response_read_file(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/read_file").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/read_file").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
with client.browsers.fs.with_streaming_response.read_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as f:
assert not f.is_closed
@@ -420,9 +432,9 @@ def test_streaming_response_read_file(self, client: Kernel, respx_mock: MockRout
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_path_params_read_file(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.browsers.fs.with_raw_response.read_file(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -430,7 +442,7 @@ def test_path_params_read_file(self, client: Kernel) -> None:
@parametrize
def test_method_set_file_permissions(self, client: Kernel) -> None:
f = client.browsers.fs.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
)
@@ -440,7 +452,7 @@ def test_method_set_file_permissions(self, client: Kernel) -> None:
@parametrize
def test_method_set_file_permissions_with_all_params(self, client: Kernel) -> None:
f = client.browsers.fs.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
group="group",
@@ -452,7 +464,7 @@ def test_method_set_file_permissions_with_all_params(self, client: Kernel) -> No
@parametrize
def test_raw_response_set_file_permissions(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
)
@@ -466,7 +478,7 @@ def test_raw_response_set_file_permissions(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_set_file_permissions(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
) as response:
@@ -481,9 +493,9 @@ def test_streaming_response_set_file_permissions(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_set_file_permissions(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.browsers.fs.with_raw_response.set_file_permissions(
- id="",
+ id_or_name="",
mode="0611",
path="/J!",
)
@@ -492,7 +504,7 @@ def test_path_params_set_file_permissions(self, client: Kernel) -> None:
@parametrize
def test_method_upload(self, client: Kernel) -> None:
f = client.browsers.fs.upload(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
files=[
{
"dest_path": "/J!",
@@ -506,7 +518,7 @@ def test_method_upload(self, client: Kernel) -> None:
@parametrize
def test_raw_response_upload(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.upload(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
files=[
{
"dest_path": "/J!",
@@ -524,7 +536,7 @@ def test_raw_response_upload(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_upload(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.upload(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
files=[
{
"dest_path": "/J!",
@@ -543,9 +555,9 @@ def test_streaming_response_upload(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_upload(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.fs.with_raw_response.upload(
- id="",
+ id_or_name="",
files=[
{
"dest_path": "/J!",
@@ -558,7 +570,7 @@ def test_path_params_upload(self, client: Kernel) -> None:
@parametrize
def test_method_upload_zip(self, client: Kernel) -> None:
f = client.browsers.fs.upload_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
zip_file=b"Example data",
)
@@ -568,7 +580,7 @@ def test_method_upload_zip(self, client: Kernel) -> None:
@parametrize
def test_raw_response_upload_zip(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.upload_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
zip_file=b"Example data",
)
@@ -582,7 +594,7 @@ def test_raw_response_upload_zip(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_upload_zip(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.upload_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
zip_file=b"Example data",
) as response:
@@ -597,9 +609,9 @@ def test_streaming_response_upload_zip(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_upload_zip(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.fs.with_raw_response.upload_zip(
- id="",
+ id_or_name="",
dest_path="/J!",
zip_file=b"Example data",
)
@@ -608,7 +620,7 @@ def test_path_params_upload_zip(self, client: Kernel) -> None:
@parametrize
def test_method_write_file(self, client: Kernel) -> None:
f = client.browsers.fs.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
)
@@ -618,7 +630,7 @@ def test_method_write_file(self, client: Kernel) -> None:
@parametrize
def test_method_write_file_with_all_params(self, client: Kernel) -> None:
f = client.browsers.fs.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
mode="0611",
@@ -629,7 +641,7 @@ def test_method_write_file_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_write_file(self, client: Kernel) -> None:
response = client.browsers.fs.with_raw_response.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
)
@@ -643,7 +655,7 @@ def test_raw_response_write_file(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_write_file(self, client: Kernel) -> None:
with client.browsers.fs.with_streaming_response.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
) as response:
@@ -658,9 +670,9 @@ def test_streaming_response_write_file(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_write_file(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.browsers.fs.with_raw_response.write_file(
- id="",
+ id_or_name="",
contents=b"Example data",
path="/J!",
)
@@ -675,7 +687,7 @@ class TestAsyncFs:
@parametrize
async def test_method_create_directory(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f is None
@@ -684,7 +696,7 @@ async def test_method_create_directory(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_create_directory_with_all_params(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
mode="0611",
)
@@ -694,7 +706,7 @@ async def test_method_create_directory_with_all_params(self, async_client: Async
@parametrize
async def test_raw_response_create_directory(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -707,7 +719,7 @@ async def test_raw_response_create_directory(self, async_client: AsyncKernel) ->
@parametrize
async def test_streaming_response_create_directory(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.create_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -721,9 +733,9 @@ async def test_streaming_response_create_directory(self, async_client: AsyncKern
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_create_directory(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.browsers.fs.with_raw_response.create_directory(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -731,7 +743,7 @@ async def test_path_params_create_directory(self, async_client: AsyncKernel) ->
@parametrize
async def test_method_delete_directory(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.delete_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f is None
@@ -740,7 +752,7 @@ async def test_method_delete_directory(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_delete_directory(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.delete_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -753,7 +765,7 @@ async def test_raw_response_delete_directory(self, async_client: AsyncKernel) ->
@parametrize
async def test_streaming_response_delete_directory(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.delete_directory(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -767,9 +779,9 @@ async def test_streaming_response_delete_directory(self, async_client: AsyncKern
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_delete_directory(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.browsers.fs.with_raw_response.delete_directory(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -777,7 +789,7 @@ async def test_path_params_delete_directory(self, async_client: AsyncKernel) ->
@parametrize
async def test_method_delete_file(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.delete_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f is None
@@ -786,7 +798,7 @@ async def test_method_delete_file(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_delete_file(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.delete_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -799,7 +811,7 @@ async def test_raw_response_delete_file(self, async_client: AsyncKernel) -> None
@parametrize
async def test_streaming_response_delete_file(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.delete_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -813,18 +825,20 @@ async def test_streaming_response_delete_file(self, async_client: AsyncKernel) -
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_delete_file(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.browsers.fs.with_raw_response.delete_file(
- id="",
+ id_or_name="",
path="/J!",
)
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_method_download_dir_zip(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/download_dir_zip").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = await async_client.browsers.fs.download_dir_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f.is_closed
@@ -835,10 +849,12 @@ async def test_method_download_dir_zip(self, async_client: AsyncKernel, respx_mo
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_raw_response_download_dir_zip(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/download_dir_zip").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = await async_client.browsers.fs.with_raw_response.download_dir_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -850,9 +866,11 @@ async def test_raw_response_download_dir_zip(self, async_client: AsyncKernel, re
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_streaming_response_download_dir_zip(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/download_dir_zip").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/download_dir_zip").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
async with async_client.browsers.fs.with_streaming_response.download_dir_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as f:
assert not f.is_closed
@@ -867,9 +885,9 @@ async def test_streaming_response_download_dir_zip(self, async_client: AsyncKern
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_path_params_download_dir_zip(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.fs.with_raw_response.download_dir_zip(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -877,7 +895,7 @@ async def test_path_params_download_dir_zip(self, async_client: AsyncKernel) ->
@parametrize
async def test_method_file_info(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.file_info(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert_matches_type(FFileInfoResponse, f, path=["response"])
@@ -886,7 +904,7 @@ async def test_method_file_info(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_file_info(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.file_info(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -899,7 +917,7 @@ async def test_raw_response_file_info(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_file_info(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.file_info(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -913,9 +931,9 @@ async def test_streaming_response_file_info(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_file_info(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.browsers.fs.with_raw_response.file_info(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -923,7 +941,7 @@ async def test_path_params_file_info(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_list_files(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.list_files(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert_matches_type(FListFilesResponse, f, path=["response"])
@@ -932,7 +950,7 @@ async def test_method_list_files(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_list_files(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.list_files(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -945,7 +963,7 @@ async def test_raw_response_list_files(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_list_files(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.list_files(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as response:
assert not response.is_closed
@@ -959,9 +977,9 @@ async def test_streaming_response_list_files(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_list_files(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.browsers.fs.with_raw_response.list_files(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -969,7 +987,7 @@ async def test_path_params_list_files(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_move(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.move(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
src_path="/J!",
)
@@ -979,7 +997,7 @@ async def test_method_move(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_move(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.move(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
src_path="/J!",
)
@@ -993,7 +1011,7 @@ async def test_raw_response_move(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_move(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.move(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
src_path="/J!",
) as response:
@@ -1008,9 +1026,9 @@ async def test_streaming_response_move(self, async_client: AsyncKernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_move(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.browsers.fs.with_raw_response.move(
- id="",
+ id_or_name="",
dest_path="/J!",
src_path="/J!",
)
@@ -1018,9 +1036,11 @@ async def test_path_params_move(self, async_client: AsyncKernel) -> None:
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_method_read_file(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/read_file").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/read_file").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = await async_client.browsers.fs.read_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
assert f.is_closed
@@ -1031,10 +1051,12 @@ async def test_method_read_file(self, async_client: AsyncKernel, respx_mock: Moc
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_raw_response_read_file(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/read_file").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/read_file").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
f = await async_client.browsers.fs.with_raw_response.read_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
)
@@ -1046,9 +1068,11 @@ async def test_raw_response_read_file(self, async_client: AsyncKernel, respx_moc
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_streaming_response_read_file(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/fs/read_file").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/fs/read_file").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
async with async_client.browsers.fs.with_streaming_response.read_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
path="/J!",
) as f:
assert not f.is_closed
@@ -1063,9 +1087,9 @@ async def test_streaming_response_read_file(self, async_client: AsyncKernel, res
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_path_params_read_file(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.browsers.fs.with_raw_response.read_file(
- id="",
+ id_or_name="",
path="/J!",
)
@@ -1073,7 +1097,7 @@ async def test_path_params_read_file(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_set_file_permissions(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
)
@@ -1083,7 +1107,7 @@ async def test_method_set_file_permissions(self, async_client: AsyncKernel) -> N
@parametrize
async def test_method_set_file_permissions_with_all_params(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
group="group",
@@ -1095,7 +1119,7 @@ async def test_method_set_file_permissions_with_all_params(self, async_client: A
@parametrize
async def test_raw_response_set_file_permissions(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
)
@@ -1109,7 +1133,7 @@ async def test_raw_response_set_file_permissions(self, async_client: AsyncKernel
@parametrize
async def test_streaming_response_set_file_permissions(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.set_file_permissions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
mode="0611",
path="/J!",
) as response:
@@ -1124,9 +1148,9 @@ async def test_streaming_response_set_file_permissions(self, async_client: Async
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_set_file_permissions(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.browsers.fs.with_raw_response.set_file_permissions(
- id="",
+ id_or_name="",
mode="0611",
path="/J!",
)
@@ -1135,7 +1159,7 @@ async def test_path_params_set_file_permissions(self, async_client: AsyncKernel)
@parametrize
async def test_method_upload(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.upload(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
files=[
{
"dest_path": "/J!",
@@ -1149,7 +1173,7 @@ async def test_method_upload(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_upload(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.upload(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
files=[
{
"dest_path": "/J!",
@@ -1167,7 +1191,7 @@ async def test_raw_response_upload(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_upload(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.upload(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
files=[
{
"dest_path": "/J!",
@@ -1186,9 +1210,9 @@ async def test_streaming_response_upload(self, async_client: AsyncKernel) -> Non
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_upload(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.fs.with_raw_response.upload(
- id="",
+ id_or_name="",
files=[
{
"dest_path": "/J!",
@@ -1201,7 +1225,7 @@ async def test_path_params_upload(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_upload_zip(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.upload_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
zip_file=b"Example data",
)
@@ -1211,7 +1235,7 @@ async def test_method_upload_zip(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_upload_zip(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.upload_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
zip_file=b"Example data",
)
@@ -1225,7 +1249,7 @@ async def test_raw_response_upload_zip(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_upload_zip(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.upload_zip(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
dest_path="/J!",
zip_file=b"Example data",
) as response:
@@ -1240,9 +1264,9 @@ async def test_streaming_response_upload_zip(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_upload_zip(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.fs.with_raw_response.upload_zip(
- id="",
+ id_or_name="",
dest_path="/J!",
zip_file=b"Example data",
)
@@ -1251,7 +1275,7 @@ async def test_path_params_upload_zip(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_write_file(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
)
@@ -1261,7 +1285,7 @@ async def test_method_write_file(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_write_file_with_all_params(self, async_client: AsyncKernel) -> None:
f = await async_client.browsers.fs.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
mode="0611",
@@ -1272,7 +1296,7 @@ async def test_method_write_file_with_all_params(self, async_client: AsyncKernel
@parametrize
async def test_raw_response_write_file(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.fs.with_raw_response.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
)
@@ -1286,7 +1310,7 @@ async def test_raw_response_write_file(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_write_file(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.fs.with_streaming_response.write_file(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
contents=b"Example data",
path="/J!",
) as response:
@@ -1301,9 +1325,9 @@ async def test_streaming_response_write_file(self, async_client: AsyncKernel) ->
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_write_file(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.browsers.fs.with_raw_response.write_file(
- id="",
+ id_or_name="",
contents=b"Example data",
path="/J!",
)
diff --git a/tests/api_resources/browsers/test_logs.py b/tests/api_resources/browsers/test_logs.py
index 27268b82..0c4bb3ea 100644
--- a/tests/api_resources/browsers/test_logs.py
+++ b/tests/api_resources/browsers/test_logs.py
@@ -19,7 +19,7 @@ class TestLogs:
@parametrize
def test_method_stream(self, client: Kernel) -> None:
log_stream = client.browsers.logs.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
)
log_stream.response.close()
@@ -28,7 +28,7 @@ def test_method_stream(self, client: Kernel) -> None:
@parametrize
def test_method_stream_with_all_params(self, client: Kernel) -> None:
log_stream = client.browsers.logs.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
follow=True,
path="path",
@@ -40,7 +40,7 @@ def test_method_stream_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_stream(self, client: Kernel) -> None:
response = client.browsers.logs.with_raw_response.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
)
@@ -52,7 +52,7 @@ def test_raw_response_stream(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_stream(self, client: Kernel) -> None:
with client.browsers.logs.with_streaming_response.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
) as response:
assert not response.is_closed
@@ -66,9 +66,9 @@ def test_streaming_response_stream(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_stream(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.logs.with_raw_response.stream(
- id="",
+ id_or_name="",
source="path",
)
@@ -82,7 +82,7 @@ class TestAsyncLogs:
@parametrize
async def test_method_stream(self, async_client: AsyncKernel) -> None:
log_stream = await async_client.browsers.logs.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
)
await log_stream.response.aclose()
@@ -91,7 +91,7 @@ async def test_method_stream(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_stream_with_all_params(self, async_client: AsyncKernel) -> None:
log_stream = await async_client.browsers.logs.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
follow=True,
path="path",
@@ -103,7 +103,7 @@ async def test_method_stream_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_stream(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.logs.with_raw_response.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
)
@@ -115,7 +115,7 @@ async def test_raw_response_stream(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_stream(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.logs.with_streaming_response.stream(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
source="path",
) as response:
assert not response.is_closed
@@ -129,8 +129,8 @@ async def test_streaming_response_stream(self, async_client: AsyncKernel) -> Non
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_stream(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.logs.with_raw_response.stream(
- id="",
+ id_or_name="",
source="path",
)
diff --git a/tests/api_resources/browsers/test_playwright.py b/tests/api_resources/browsers/test_playwright.py
index fd91b832..3a2c457f 100644
--- a/tests/api_resources/browsers/test_playwright.py
+++ b/tests/api_resources/browsers/test_playwright.py
@@ -21,7 +21,7 @@ class TestPlaywright:
@parametrize
def test_method_execute(self, client: Kernel) -> None:
playwright = client.browsers.playwright.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
)
assert_matches_type(PlaywrightExecuteResponse, playwright, path=["response"])
@@ -30,8 +30,9 @@ def test_method_execute(self, client: Kernel) -> None:
@parametrize
def test_method_execute_with_all_params(self, client: Kernel) -> None:
playwright = client.browsers.playwright.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
+ executor="checkout",
timeout_sec=1,
)
assert_matches_type(PlaywrightExecuteResponse, playwright, path=["response"])
@@ -40,7 +41,7 @@ def test_method_execute_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_execute(self, client: Kernel) -> None:
response = client.browsers.playwright.with_raw_response.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
)
@@ -53,7 +54,7 @@ def test_raw_response_execute(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_execute(self, client: Kernel) -> None:
with client.browsers.playwright.with_streaming_response.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
) as response:
assert not response.is_closed
@@ -67,9 +68,9 @@ def test_streaming_response_execute(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_execute(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.browsers.playwright.with_raw_response.execute(
- id="",
+ id_or_name="",
code="code",
)
@@ -83,7 +84,7 @@ class TestAsyncPlaywright:
@parametrize
async def test_method_execute(self, async_client: AsyncKernel) -> None:
playwright = await async_client.browsers.playwright.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
)
assert_matches_type(PlaywrightExecuteResponse, playwright, path=["response"])
@@ -92,8 +93,9 @@ async def test_method_execute(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_execute_with_all_params(self, async_client: AsyncKernel) -> None:
playwright = await async_client.browsers.playwright.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
+ executor="checkout",
timeout_sec=1,
)
assert_matches_type(PlaywrightExecuteResponse, playwright, path=["response"])
@@ -102,7 +104,7 @@ async def test_method_execute_with_all_params(self, async_client: AsyncKernel) -
@parametrize
async def test_raw_response_execute(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.playwright.with_raw_response.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
)
@@ -115,7 +117,7 @@ async def test_raw_response_execute(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_execute(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.playwright.with_streaming_response.execute(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
) as response:
assert not response.is_closed
@@ -129,8 +131,8 @@ async def test_streaming_response_execute(self, async_client: AsyncKernel) -> No
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_execute(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.browsers.playwright.with_raw_response.execute(
- id="",
+ id_or_name="",
code="code",
)
diff --git a/tests/api_resources/browsers/test_process.py b/tests/api_resources/browsers/test_process.py
index 0418d219..bbcd7860 100644
--- a/tests/api_resources/browsers/test_process.py
+++ b/tests/api_resources/browsers/test_process.py
@@ -28,7 +28,7 @@ class TestProcess:
@parametrize
def test_method_exec(self, client: Kernel) -> None:
process = client.browsers.process.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
assert_matches_type(ProcessExecResponse, process, path=["response"])
@@ -37,7 +37,7 @@ def test_method_exec(self, client: Kernel) -> None:
@parametrize
def test_method_exec_with_all_params(self, client: Kernel) -> None:
process = client.browsers.process.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
args=["string"],
as_root=True,
@@ -52,7 +52,7 @@ def test_method_exec_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_exec(self, client: Kernel) -> None:
response = client.browsers.process.with_raw_response.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
@@ -65,7 +65,7 @@ def test_raw_response_exec(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_exec(self, client: Kernel) -> None:
with client.browsers.process.with_streaming_response.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
) as response:
assert not response.is_closed
@@ -79,9 +79,9 @@ def test_streaming_response_exec(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_exec(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.process.with_raw_response.exec(
- id="",
+ id_or_name="",
command="command",
)
@@ -90,7 +90,7 @@ def test_path_params_exec(self, client: Kernel) -> None:
def test_method_kill(self, client: Kernel) -> None:
process = client.browsers.process.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
)
assert_matches_type(ProcessKillResponse, process, path=["response"])
@@ -100,7 +100,7 @@ def test_method_kill(self, client: Kernel) -> None:
def test_raw_response_kill(self, client: Kernel) -> None:
response = client.browsers.process.with_raw_response.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
)
@@ -114,7 +114,7 @@ def test_raw_response_kill(self, client: Kernel) -> None:
def test_streaming_response_kill(self, client: Kernel) -> None:
with client.browsers.process.with_streaming_response.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
) as response:
assert not response.is_closed
@@ -128,17 +128,17 @@ def test_streaming_response_kill(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_kill(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.process.with_raw_response.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
signal="TERM",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
client.browsers.process.with_raw_response.kill(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
)
@@ -147,7 +147,7 @@ def test_path_params_kill(self, client: Kernel) -> None:
def test_method_resize(self, client: Kernel) -> None:
process = client.browsers.process.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
)
@@ -158,7 +158,7 @@ def test_method_resize(self, client: Kernel) -> None:
def test_raw_response_resize(self, client: Kernel) -> None:
response = client.browsers.process.with_raw_response.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
)
@@ -173,7 +173,7 @@ def test_raw_response_resize(self, client: Kernel) -> None:
def test_streaming_response_resize(self, client: Kernel) -> None:
with client.browsers.process.with_streaming_response.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
) as response:
@@ -188,10 +188,10 @@ def test_streaming_response_resize(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_resize(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.browsers.process.with_raw_response.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
cols=1,
rows=1,
)
@@ -199,7 +199,7 @@ def test_path_params_resize(self, client: Kernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
client.browsers.process.with_raw_response.resize(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
)
@@ -208,7 +208,7 @@ def test_path_params_resize(self, client: Kernel) -> None:
@parametrize
def test_method_spawn(self, client: Kernel) -> None:
process = client.browsers.process.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
assert_matches_type(ProcessSpawnResponse, process, path=["response"])
@@ -217,7 +217,7 @@ def test_method_spawn(self, client: Kernel) -> None:
@parametrize
def test_method_spawn_with_all_params(self, client: Kernel) -> None:
process = client.browsers.process.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
allocate_tty=True,
args=["string"],
@@ -235,7 +235,7 @@ def test_method_spawn_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_spawn(self, client: Kernel) -> None:
response = client.browsers.process.with_raw_response.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
@@ -248,7 +248,7 @@ def test_raw_response_spawn(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_spawn(self, client: Kernel) -> None:
with client.browsers.process.with_streaming_response.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
) as response:
assert not response.is_closed
@@ -262,9 +262,9 @@ def test_streaming_response_spawn(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_spawn(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.process.with_raw_response.spawn(
- id="",
+ id_or_name="",
command="command",
)
@@ -273,7 +273,7 @@ def test_path_params_spawn(self, client: Kernel) -> None:
def test_method_status(self, client: Kernel) -> None:
process = client.browsers.process.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ProcessStatusResponse, process, path=["response"])
@@ -282,7 +282,7 @@ def test_method_status(self, client: Kernel) -> None:
def test_raw_response_status(self, client: Kernel) -> None:
response = client.browsers.process.with_raw_response.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -295,7 +295,7 @@ def test_raw_response_status(self, client: Kernel) -> None:
def test_streaming_response_status(self, client: Kernel) -> None:
with client.browsers.process.with_streaming_response.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -308,16 +308,16 @@ def test_streaming_response_status(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_status(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.process.with_raw_response.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
client.browsers.process.with_raw_response.status(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -325,7 +325,7 @@ def test_path_params_status(self, client: Kernel) -> None:
def test_method_stdin(self, client: Kernel) -> None:
process = client.browsers.process.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
)
assert_matches_type(ProcessStdinResponse, process, path=["response"])
@@ -335,7 +335,7 @@ def test_method_stdin(self, client: Kernel) -> None:
def test_raw_response_stdin(self, client: Kernel) -> None:
response = client.browsers.process.with_raw_response.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
)
@@ -349,7 +349,7 @@ def test_raw_response_stdin(self, client: Kernel) -> None:
def test_streaming_response_stdin(self, client: Kernel) -> None:
with client.browsers.process.with_streaming_response.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
) as response:
assert not response.is_closed
@@ -363,17 +363,17 @@ def test_streaming_response_stdin(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_stdin(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.process.with_raw_response.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
data_b64="data_b64",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
client.browsers.process.with_raw_response.stdin(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
)
@@ -382,7 +382,7 @@ def test_path_params_stdin(self, client: Kernel) -> None:
def test_method_stdout_stream(self, client: Kernel) -> None:
process_stream = client.browsers.process.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
process_stream.response.close()
@@ -391,7 +391,7 @@ def test_method_stdout_stream(self, client: Kernel) -> None:
def test_raw_response_stdout_stream(self, client: Kernel) -> None:
response = client.browsers.process.with_raw_response.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -403,7 +403,7 @@ def test_raw_response_stdout_stream(self, client: Kernel) -> None:
def test_streaming_response_stdout_stream(self, client: Kernel) -> None:
with client.browsers.process.with_streaming_response.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -416,16 +416,16 @@ def test_streaming_response_stdout_stream(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_stdout_stream(self, client: Kernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browsers.process.with_raw_response.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
client.browsers.process.with_raw_response.stdout_stream(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@@ -438,7 +438,7 @@ class TestAsyncProcess:
@parametrize
async def test_method_exec(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
assert_matches_type(ProcessExecResponse, process, path=["response"])
@@ -447,7 +447,7 @@ async def test_method_exec(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_exec_with_all_params(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
args=["string"],
as_root=True,
@@ -462,7 +462,7 @@ async def test_method_exec_with_all_params(self, async_client: AsyncKernel) -> N
@parametrize
async def test_raw_response_exec(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.process.with_raw_response.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
@@ -475,7 +475,7 @@ async def test_raw_response_exec(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_exec(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.process.with_streaming_response.exec(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
) as response:
assert not response.is_closed
@@ -489,9 +489,9 @@ async def test_streaming_response_exec(self, async_client: AsyncKernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_exec(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.process.with_raw_response.exec(
- id="",
+ id_or_name="",
command="command",
)
@@ -500,7 +500,7 @@ async def test_path_params_exec(self, async_client: AsyncKernel) -> None:
async def test_method_kill(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
)
assert_matches_type(ProcessKillResponse, process, path=["response"])
@@ -510,7 +510,7 @@ async def test_method_kill(self, async_client: AsyncKernel) -> None:
async def test_raw_response_kill(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.process.with_raw_response.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
)
@@ -524,7 +524,7 @@ async def test_raw_response_kill(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_kill(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.process.with_streaming_response.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
) as response:
assert not response.is_closed
@@ -538,17 +538,17 @@ async def test_streaming_response_kill(self, async_client: AsyncKernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_kill(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.process.with_raw_response.kill(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
signal="TERM",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
await async_client.browsers.process.with_raw_response.kill(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
signal="TERM",
)
@@ -557,7 +557,7 @@ async def test_path_params_kill(self, async_client: AsyncKernel) -> None:
async def test_method_resize(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
)
@@ -568,7 +568,7 @@ async def test_method_resize(self, async_client: AsyncKernel) -> None:
async def test_raw_response_resize(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.process.with_raw_response.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
)
@@ -583,7 +583,7 @@ async def test_raw_response_resize(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_resize(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.process.with_streaming_response.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
) as response:
@@ -598,10 +598,10 @@ async def test_streaming_response_resize(self, async_client: AsyncKernel) -> Non
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_resize(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.browsers.process.with_raw_response.resize(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
cols=1,
rows=1,
)
@@ -609,7 +609,7 @@ async def test_path_params_resize(self, async_client: AsyncKernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
await async_client.browsers.process.with_raw_response.resize(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
cols=1,
rows=1,
)
@@ -618,7 +618,7 @@ async def test_path_params_resize(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_spawn(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
assert_matches_type(ProcessSpawnResponse, process, path=["response"])
@@ -627,7 +627,7 @@ async def test_method_spawn(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_spawn_with_all_params(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
allocate_tty=True,
args=["string"],
@@ -645,7 +645,7 @@ async def test_method_spawn_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_spawn(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.process.with_raw_response.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
)
@@ -658,7 +658,7 @@ async def test_raw_response_spawn(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_spawn(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.process.with_streaming_response.spawn(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
command="command",
) as response:
assert not response.is_closed
@@ -672,9 +672,9 @@ async def test_streaming_response_spawn(self, async_client: AsyncKernel) -> None
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_spawn(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.process.with_raw_response.spawn(
- id="",
+ id_or_name="",
command="command",
)
@@ -683,7 +683,7 @@ async def test_path_params_spawn(self, async_client: AsyncKernel) -> None:
async def test_method_status(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ProcessStatusResponse, process, path=["response"])
@@ -692,7 +692,7 @@ async def test_method_status(self, async_client: AsyncKernel) -> None:
async def test_raw_response_status(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.process.with_raw_response.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -705,7 +705,7 @@ async def test_raw_response_status(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_status(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.process.with_streaming_response.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -718,16 +718,16 @@ async def test_streaming_response_status(self, async_client: AsyncKernel) -> Non
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_status(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.process.with_raw_response.status(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
await async_client.browsers.process.with_raw_response.status(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -735,7 +735,7 @@ async def test_path_params_status(self, async_client: AsyncKernel) -> None:
async def test_method_stdin(self, async_client: AsyncKernel) -> None:
process = await async_client.browsers.process.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
)
assert_matches_type(ProcessStdinResponse, process, path=["response"])
@@ -745,7 +745,7 @@ async def test_method_stdin(self, async_client: AsyncKernel) -> None:
async def test_raw_response_stdin(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.process.with_raw_response.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
)
@@ -759,7 +759,7 @@ async def test_raw_response_stdin(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_stdin(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.process.with_streaming_response.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
) as response:
assert not response.is_closed
@@ -773,17 +773,17 @@ async def test_streaming_response_stdin(self, async_client: AsyncKernel) -> None
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_stdin(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.process.with_raw_response.stdin(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
data_b64="data_b64",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
await async_client.browsers.process.with_raw_response.stdin(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
data_b64="data_b64",
)
@@ -792,7 +792,7 @@ async def test_path_params_stdin(self, async_client: AsyncKernel) -> None:
async def test_method_stdout_stream(self, async_client: AsyncKernel) -> None:
process_stream = await async_client.browsers.process.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
await process_stream.response.aclose()
@@ -801,7 +801,7 @@ async def test_method_stdout_stream(self, async_client: AsyncKernel) -> None:
async def test_raw_response_stdout_stream(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.process.with_raw_response.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -813,7 +813,7 @@ async def test_raw_response_stdout_stream(self, async_client: AsyncKernel) -> No
async def test_streaming_response_stdout_stream(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.process.with_streaming_response.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -826,14 +826,14 @@ async def test_streaming_response_stdout_stream(self, async_client: AsyncKernel)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_stdout_stream(self, async_client: AsyncKernel) -> None:
- with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browsers.process.with_raw_response.stdout_stream(
process_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `process_id` but received ''"):
await async_client.browsers.process.with_raw_response.stdout_stream(
process_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
diff --git a/tests/api_resources/browsers/test_replays.py b/tests/api_resources/browsers/test_replays.py
index 72e986c1..bb6ddbdd 100644
--- a/tests/api_resources/browsers/test_replays.py
+++ b/tests/api_resources/browsers/test_replays.py
@@ -29,7 +29,7 @@ class TestReplays:
@parametrize
def test_method_list(self, client: Kernel) -> None:
replay = client.browsers.replays.list(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ReplayListResponse, replay, path=["response"])
@@ -37,7 +37,7 @@ def test_method_list(self, client: Kernel) -> None:
@parametrize
def test_raw_response_list(self, client: Kernel) -> None:
response = client.browsers.replays.with_raw_response.list(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -49,7 +49,7 @@ def test_raw_response_list(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_list(self, client: Kernel) -> None:
with client.browsers.replays.with_streaming_response.list(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -62,7 +62,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_list(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.browsers.replays.with_raw_response.list(
"",
)
@@ -70,10 +70,12 @@ def test_path_params_list(self, client: Kernel) -> None:
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_method_download(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/replays/replay_id").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/replays/replay_id").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
replay = client.browsers.replays.download(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert replay.is_closed
assert replay.json() == {"foo": "bar"}
@@ -83,11 +85,13 @@ def test_method_download(self, client: Kernel, respx_mock: MockRouter) -> None:
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/replays/replay_id").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/replays/replay_id").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
replay = client.browsers.replays.with_raw_response.download(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert replay.is_closed is True
@@ -98,10 +102,12 @@ def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) ->
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_streaming_response_download(self, client: Kernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/replays/replay_id").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/replays/replay_id").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
with client.browsers.replays.with_streaming_response.download(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as replay:
assert not replay.is_closed
assert replay.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -115,23 +121,23 @@ def test_streaming_response_download(self, client: Kernel, respx_mock: MockRoute
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_path_params_download(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.browsers.replays.with_raw_response.download(
replay_id="replay_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `replay_id` but received ''"):
client.browsers.replays.with_raw_response.download(
replay_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_start(self, client: Kernel) -> None:
replay = client.browsers.replays.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ReplayStartResponse, replay, path=["response"])
@@ -139,9 +145,10 @@ def test_method_start(self, client: Kernel) -> None:
@parametrize
def test_method_start_with_all_params(self, client: Kernel) -> None:
replay = client.browsers.replays.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
framerate=1,
max_duration_in_seconds=1,
+ record_audio=True,
)
assert_matches_type(ReplayStartResponse, replay, path=["response"])
@@ -149,7 +156,7 @@ def test_method_start_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_start(self, client: Kernel) -> None:
response = client.browsers.replays.with_raw_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -161,7 +168,7 @@ def test_raw_response_start(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_start(self, client: Kernel) -> None:
with client.browsers.replays.with_streaming_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -174,9 +181,9 @@ def test_streaming_response_start(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_start(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.browsers.replays.with_raw_response.start(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -184,7 +191,7 @@ def test_path_params_start(self, client: Kernel) -> None:
def test_method_stop(self, client: Kernel) -> None:
replay = client.browsers.replays.stop(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert replay is None
@@ -193,7 +200,7 @@ def test_method_stop(self, client: Kernel) -> None:
def test_raw_response_stop(self, client: Kernel) -> None:
response = client.browsers.replays.with_raw_response.stop(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -206,7 +213,7 @@ def test_raw_response_stop(self, client: Kernel) -> None:
def test_streaming_response_stop(self, client: Kernel) -> None:
with client.browsers.replays.with_streaming_response.stop(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -219,16 +226,16 @@ def test_streaming_response_stop(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_stop(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.browsers.replays.with_raw_response.stop(
replay_id="replay_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `replay_id` but received ''"):
client.browsers.replays.with_raw_response.stop(
replay_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@@ -241,7 +248,7 @@ class TestAsyncReplays:
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
replay = await async_client.browsers.replays.list(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ReplayListResponse, replay, path=["response"])
@@ -249,7 +256,7 @@ async def test_method_list(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.replays.with_raw_response.list(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -261,7 +268,7 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.replays.with_streaming_response.list(
- "id",
+ "htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -274,7 +281,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_list(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.browsers.replays.with_raw_response.list(
"",
)
@@ -282,10 +289,12 @@ async def test_path_params_list(self, async_client: AsyncKernel) -> None:
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_method_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/replays/replay_id").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/replays/replay_id").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
replay = await async_client.browsers.replays.download(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert replay.is_closed
assert await replay.json() == {"foo": "bar"}
@@ -295,11 +304,13 @@ async def test_method_download(self, async_client: AsyncKernel, respx_mock: Mock
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/replays/replay_id").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/replays/replay_id").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
replay = await async_client.browsers.replays.with_raw_response.download(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert replay.is_closed is True
@@ -310,10 +321,12 @@ async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_streaming_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
- respx_mock.get("/browsers/id/replays/replay_id").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ respx_mock.get("/browsers/htzv5orfit78e1m2biiifpbv/replays/replay_id").mock(
+ return_value=httpx.Response(200, json={"foo": "bar"})
+ )
async with async_client.browsers.replays.with_streaming_response.download(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as replay:
assert not replay.is_closed
assert replay.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -327,23 +340,23 @@ async def test_streaming_response_download(self, async_client: AsyncKernel, resp
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_path_params_download(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.browsers.replays.with_raw_response.download(
replay_id="replay_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `replay_id` but received ''"):
await async_client.browsers.replays.with_raw_response.download(
replay_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_start(self, async_client: AsyncKernel) -> None:
replay = await async_client.browsers.replays.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(ReplayStartResponse, replay, path=["response"])
@@ -351,9 +364,10 @@ async def test_method_start(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_start_with_all_params(self, async_client: AsyncKernel) -> None:
replay = await async_client.browsers.replays.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
framerate=1,
max_duration_in_seconds=1,
+ record_audio=True,
)
assert_matches_type(ReplayStartResponse, replay, path=["response"])
@@ -361,7 +375,7 @@ async def test_method_start_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_start(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.replays.with_raw_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -373,7 +387,7 @@ async def test_raw_response_start(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_start(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.replays.with_streaming_response.start(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -386,9 +400,9 @@ async def test_streaming_response_start(self, async_client: AsyncKernel) -> None
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_start(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.browsers.replays.with_raw_response.start(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -396,7 +410,7 @@ async def test_path_params_start(self, async_client: AsyncKernel) -> None:
async def test_method_stop(self, async_client: AsyncKernel) -> None:
replay = await async_client.browsers.replays.stop(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert replay is None
@@ -405,7 +419,7 @@ async def test_method_stop(self, async_client: AsyncKernel) -> None:
async def test_raw_response_stop(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.replays.with_raw_response.stop(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -418,7 +432,7 @@ async def test_raw_response_stop(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_stop(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.replays.with_streaming_response.stop(
replay_id="replay_id",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -431,14 +445,14 @@ async def test_streaming_response_stop(self, async_client: AsyncKernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_stop(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.browsers.replays.with_raw_response.stop(
replay_id="replay_id",
- id="",
+ id_or_name="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `replay_id` but received ''"):
await async_client.browsers.replays.with_raw_response.stop(
replay_id="",
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
diff --git a/tests/api_resources/browsers/test_telemetry.py b/tests/api_resources/browsers/test_telemetry.py
new file mode 100644
index 00000000..62742f45
--- /dev/null
+++ b/tests/api_resources/browsers/test_telemetry.py
@@ -0,0 +1,243 @@
+# 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.pagination import SyncOffsetPagination, AsyncOffsetPagination
+from kernel.types.browsers import TelemetryEventsResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestTelemetry:
+ 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_events(self, client: Kernel) -> None:
+ telemetry = client.browsers.telemetry.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(SyncOffsetPagination[TelemetryEventsResponse], telemetry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_events_with_all_params(self, client: Kernel) -> None:
+ telemetry = client.browsers.telemetry.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ category=["console"],
+ limit=1,
+ offset=0,
+ order="order",
+ since="since",
+ type=["string"],
+ until="until",
+ )
+ assert_matches_type(SyncOffsetPagination[TelemetryEventsResponse], telemetry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_events(self, client: Kernel) -> None:
+ response = client.browsers.telemetry.with_raw_response.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ telemetry = response.parse()
+ assert_matches_type(SyncOffsetPagination[TelemetryEventsResponse], telemetry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_events(self, client: Kernel) -> None:
+ with client.browsers.telemetry.with_streaming_response.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ telemetry = response.parse()
+ assert_matches_type(SyncOffsetPagination[TelemetryEventsResponse], telemetry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_events(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.telemetry.with_raw_response.events(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_stream(self, client: Kernel) -> None:
+ telemetry_stream = client.browsers.telemetry.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ telemetry_stream.response.close()
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_stream_with_all_params(self, client: Kernel) -> None:
+ telemetry_stream = client.browsers.telemetry.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ replay="replay",
+ type=["string"],
+ last_event_id="Last-Event-ID",
+ )
+ telemetry_stream.response.close()
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_stream(self, client: Kernel) -> None:
+ response = client.browsers.telemetry.with_raw_response.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ stream = response.parse()
+ stream.close()
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_stream(self, client: Kernel) -> None:
+ with client.browsers.telemetry.with_streaming_response.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ stream = response.parse()
+ stream.close()
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_stream(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.telemetry.with_raw_response.stream(
+ id_or_name="",
+ )
+
+
+class TestAsyncTelemetry:
+ 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_events(self, async_client: AsyncKernel) -> None:
+ telemetry = await async_client.browsers.telemetry.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(AsyncOffsetPagination[TelemetryEventsResponse], telemetry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_events_with_all_params(self, async_client: AsyncKernel) -> None:
+ telemetry = await async_client.browsers.telemetry.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ category=["console"],
+ limit=1,
+ offset=0,
+ order="order",
+ since="since",
+ type=["string"],
+ until="until",
+ )
+ assert_matches_type(AsyncOffsetPagination[TelemetryEventsResponse], telemetry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_events(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.telemetry.with_raw_response.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ telemetry = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[TelemetryEventsResponse], telemetry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_events(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.telemetry.with_streaming_response.events(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ telemetry = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[TelemetryEventsResponse], telemetry, 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_events(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.telemetry.with_raw_response.events(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_stream(self, async_client: AsyncKernel) -> None:
+ telemetry_stream = await async_client.browsers.telemetry.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ await telemetry_stream.response.aclose()
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_stream_with_all_params(self, async_client: AsyncKernel) -> None:
+ telemetry_stream = await async_client.browsers.telemetry.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ replay="replay",
+ type=["string"],
+ last_event_id="Last-Event-ID",
+ )
+ await telemetry_stream.response.aclose()
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_stream(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.telemetry.with_raw_response.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ stream = await response.parse()
+ await stream.close()
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_stream(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.telemetry.with_streaming_response.stream(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ stream = await response.parse()
+ await stream.close()
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_stream(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.telemetry.with_raw_response.stream(
+ id_or_name="",
+ )
diff --git a/tests/api_resources/browsers/test_webmcp.py b/tests/api_resources/browsers/test_webmcp.py
new file mode 100644
index 00000000..ac354bca
--- /dev/null
+++ b/tests/api_resources/browsers/test_webmcp.py
@@ -0,0 +1,248 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from kernel import Kernel, AsyncKernel
+from tests.utils import assert_matches_type
+from kernel.types.browsers import ToolsResponse, InvocationResult
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestWebmcp:
+ 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_invoke_tool(self, client: Kernel) -> None:
+ webmcp = client.browsers.webmcp.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ )
+ assert_matches_type(InvocationResult, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_invoke_tool_with_all_params(self, client: Kernel) -> None:
+ webmcp = client.browsers.webmcp.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ timeout_sec=1,
+ )
+ assert_matches_type(InvocationResult, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_invoke_tool(self, client: Kernel) -> None:
+ response = client.browsers.webmcp.with_raw_response.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webmcp = response.parse()
+ assert_matches_type(InvocationResult, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_invoke_tool(self, client: Kernel) -> None:
+ with client.browsers.webmcp.with_streaming_response.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webmcp = response.parse()
+ assert_matches_type(InvocationResult, webmcp, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_invoke_tool(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.webmcp.with_raw_response.invoke_tool(
+ id_or_name="",
+ input={"foo": "bar"},
+ tool_ref="x",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_tools(self, client: Kernel) -> None:
+ webmcp = client.browsers.webmcp.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(ToolsResponse, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_tools_with_all_params(self, client: Kernel) -> None:
+ webmcp = client.browsers.webmcp.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ exclude_custom=True,
+ )
+ assert_matches_type(ToolsResponse, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list_tools(self, client: Kernel) -> None:
+ response = client.browsers.webmcp.with_raw_response.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webmcp = response.parse()
+ assert_matches_type(ToolsResponse, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list_tools(self, client: Kernel) -> None:
+ with client.browsers.webmcp.with_streaming_response.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webmcp = response.parse()
+ assert_matches_type(ToolsResponse, webmcp, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list_tools(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.webmcp.with_raw_response.list_tools(
+ id_or_name="",
+ )
+
+
+class TestAsyncWebmcp:
+ 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_invoke_tool(self, async_client: AsyncKernel) -> None:
+ webmcp = await async_client.browsers.webmcp.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ )
+ assert_matches_type(InvocationResult, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_invoke_tool_with_all_params(self, async_client: AsyncKernel) -> None:
+ webmcp = await async_client.browsers.webmcp.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ timeout_sec=1,
+ )
+ assert_matches_type(InvocationResult, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_invoke_tool(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.webmcp.with_raw_response.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webmcp = await response.parse()
+ assert_matches_type(InvocationResult, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_invoke_tool(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.webmcp.with_streaming_response.invoke_tool(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ input={"foo": "bar"},
+ tool_ref="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webmcp = await response.parse()
+ assert_matches_type(InvocationResult, webmcp, 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_invoke_tool(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.webmcp.with_raw_response.invoke_tool(
+ id_or_name="",
+ input={"foo": "bar"},
+ tool_ref="x",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_tools(self, async_client: AsyncKernel) -> None:
+ webmcp = await async_client.browsers.webmcp.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(ToolsResponse, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_tools_with_all_params(self, async_client: AsyncKernel) -> None:
+ webmcp = await async_client.browsers.webmcp.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ exclude_custom=True,
+ )
+ assert_matches_type(ToolsResponse, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list_tools(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.webmcp.with_raw_response.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webmcp = await response.parse()
+ assert_matches_type(ToolsResponse, webmcp, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_tools(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.webmcp.with_streaming_response.list_tools(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webmcp = await response.parse()
+ assert_matches_type(ToolsResponse, webmcp, 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_list_tools(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.webmcp.with_raw_response.list_tools(
+ id_or_name="",
+ )
diff --git a/tests/api_resources/browsers/webmcp/__init__.py b/tests/api_resources/browsers/webmcp/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/browsers/webmcp/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/browsers/webmcp/test_custom_tools.py b/tests/api_resources/browsers/webmcp/test_custom_tools.py
new file mode 100644
index 00000000..49b3b4ee
--- /dev/null
+++ b/tests/api_resources/browsers/webmcp/test_custom_tools.py
@@ -0,0 +1,334 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from kernel import Kernel, AsyncKernel
+from tests.utils import assert_matches_type
+from kernel.types.browsers.webmcp import CustomToolsResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCustomTools:
+ 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_list(self, client: Kernel) -> None:
+ custom_tool = client.browsers.webmcp.custom_tools.list(
+ "id_or_name",
+ )
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.browsers.webmcp.custom_tools.with_raw_response.list(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ custom_tool = response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.browsers.webmcp.custom_tools.with_streaming_response.list(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ custom_tool = response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.webmcp.custom_tools.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_add(self, client: Kernel) -> None:
+ custom_tool = client.browsers.webmcp.custom_tools.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ )
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_add_with_all_params(self, client: Kernel) -> None:
+ custom_tool = client.browsers.webmcp.custom_tools.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ force_overwrite_namespace=True,
+ )
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_add(self, client: Kernel) -> None:
+ response = client.browsers.webmcp.custom_tools.with_raw_response.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ custom_tool = response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_add(self, client: Kernel) -> None:
+ with client.browsers.webmcp.custom_tools.with_streaming_response.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ custom_tool = response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_add(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.webmcp.custom_tools.with_raw_response.add(
+ id_or_name="",
+ namespace="namespace",
+ source="source",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_remove(self, client: Kernel) -> None:
+ custom_tool = client.browsers.webmcp.custom_tools.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="id_or_name",
+ )
+ assert custom_tool is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_remove(self, client: Kernel) -> None:
+ response = client.browsers.webmcp.custom_tools.with_raw_response.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ custom_tool = response.parse()
+ assert custom_tool is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_remove(self, client: Kernel) -> None:
+ with client.browsers.webmcp.custom_tools.with_streaming_response.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ custom_tool = response.parse()
+ assert custom_tool is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_remove(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.webmcp.custom_tools.with_raw_response.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.browsers.webmcp.custom_tools.with_raw_response.remove(
+ id="",
+ id_or_name="id_or_name",
+ )
+
+
+class TestAsyncCustomTools:
+ 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_list(self, async_client: AsyncKernel) -> None:
+ custom_tool = await async_client.browsers.webmcp.custom_tools.list(
+ "id_or_name",
+ )
+ assert_matches_type(CustomToolsResponse, custom_tool, 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.browsers.webmcp.custom_tools.with_raw_response.list(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ custom_tool = await response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, 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.browsers.webmcp.custom_tools.with_streaming_response.list(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ custom_tool = await response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, 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_list(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.webmcp.custom_tools.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_add(self, async_client: AsyncKernel) -> None:
+ custom_tool = await async_client.browsers.webmcp.custom_tools.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ )
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_add_with_all_params(self, async_client: AsyncKernel) -> None:
+ custom_tool = await async_client.browsers.webmcp.custom_tools.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ force_overwrite_namespace=True,
+ )
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_add(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.webmcp.custom_tools.with_raw_response.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ custom_tool = await response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_add(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.webmcp.custom_tools.with_streaming_response.add(
+ id_or_name="id_or_name",
+ namespace="namespace",
+ source="source",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ custom_tool = await response.parse()
+ assert_matches_type(CustomToolsResponse, custom_tool, 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_add(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.webmcp.custom_tools.with_raw_response.add(
+ id_or_name="",
+ namespace="namespace",
+ source="source",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncKernel) -> None:
+ custom_tool = await async_client.browsers.webmcp.custom_tools.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="id_or_name",
+ )
+ assert custom_tool is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.webmcp.custom_tools.with_raw_response.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ custom_tool = await response.parse()
+ assert custom_tool is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.webmcp.custom_tools.with_streaming_response.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ custom_tool = await response.parse()
+ assert custom_tool 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_remove(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.webmcp.custom_tools.with_raw_response.remove(
+ id="ct_n10b9798ad53ecc4y69z31e1",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.browsers.webmcp.custom_tools.with_raw_response.remove(
+ id="",
+ id_or_name="id_or_name",
+ )
diff --git a/tests/api_resources/config_registry/__init__.py b/tests/api_resources/config_registry/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/config_registry/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/config_registry/test_analyses.py b/tests/api_resources/config_registry/test_analyses.py
new file mode 100644
index 00000000..0a9c8607
--- /dev/null
+++ b/tests/api_resources/config_registry/test_analyses.py
@@ -0,0 +1,269 @@
+# 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 AnalysisSummary, ConfigRegistryResponse
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAnalyses:
+ 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:
+ analysis = client.config_registry.analyses.retrieve(
+ "id",
+ )
+ assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.config_registry.analyses.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ analysis = response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.config_registry.analyses.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ analysis = response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, 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.config_registry.analyses.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ analysis = client.config_registry.analyses.list()
+ assert_matches_type(SyncOffsetPagination[AnalysisSummary], analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ analysis = client.config_registry.analyses.list(
+ limit=1,
+ offset=0,
+ search="search",
+ )
+ assert_matches_type(SyncOffsetPagination[AnalysisSummary], analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.config_registry.analyses.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ analysis = response.parse()
+ assert_matches_type(SyncOffsetPagination[AnalysisSummary], analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.config_registry.analyses.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ analysis = response.parse()
+ assert_matches_type(SyncOffsetPagination[AnalysisSummary], analysis, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_cancel(self, client: Kernel) -> None:
+ analysis = client.config_registry.analyses.cancel(
+ "id",
+ )
+ assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_cancel(self, client: Kernel) -> None:
+ response = client.config_registry.analyses.with_raw_response.cancel(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ analysis = response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_cancel(self, client: Kernel) -> None:
+ with client.config_registry.analyses.with_streaming_response.cancel(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ analysis = response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_cancel(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.config_registry.analyses.with_raw_response.cancel(
+ "",
+ )
+
+
+class TestAsyncAnalyses:
+ 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:
+ analysis = await async_client.config_registry.analyses.retrieve(
+ "id",
+ )
+ assert_matches_type(ConfigRegistryResponse, analysis, 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.config_registry.analyses.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ analysis = await response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, 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.config_registry.analyses.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ analysis = await response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, 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.config_registry.analyses.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ analysis = await async_client.config_registry.analyses.list()
+ assert_matches_type(AsyncOffsetPagination[AnalysisSummary], analysis, 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:
+ analysis = await async_client.config_registry.analyses.list(
+ limit=1,
+ offset=0,
+ search="search",
+ )
+ assert_matches_type(AsyncOffsetPagination[AnalysisSummary], analysis, 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.config_registry.analyses.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ analysis = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[AnalysisSummary], analysis, 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.config_registry.analyses.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ analysis = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[AnalysisSummary], analysis, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncKernel) -> None:
+ analysis = await async_client.config_registry.analyses.cancel(
+ "id",
+ )
+ assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncKernel) -> None:
+ response = await async_client.config_registry.analyses.with_raw_response.cancel(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ analysis = await response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncKernel) -> None:
+ async with async_client.config_registry.analyses.with_streaming_response.cancel(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ analysis = await response.parse()
+ assert_matches_type(ConfigRegistryResponse, analysis, 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_cancel(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.config_registry.analyses.with_raw_response.cancel(
+ "",
+ )
diff --git a/tests/api_resources/organization/__init__.py b/tests/api_resources/organization/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/organization/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/organization/test_entitlements.py b/tests/api_resources/organization/test_entitlements.py
new file mode 100644
index 00000000..d788bc66
--- /dev/null
+++ b/tests/api_resources/organization/test_entitlements.py
@@ -0,0 +1,80 @@
+# 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.organization import OrgEntitlements
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEntitlements:
+ 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:
+ entitlement = client.organization.entitlements.retrieve()
+ assert_matches_type(OrgEntitlements, entitlement, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.organization.entitlements.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entitlement = response.parse()
+ assert_matches_type(OrgEntitlements, entitlement, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.organization.entitlements.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entitlement = response.parse()
+ assert_matches_type(OrgEntitlements, entitlement, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncEntitlements:
+ 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:
+ entitlement = await async_client.organization.entitlements.retrieve()
+ assert_matches_type(OrgEntitlements, entitlement, 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.organization.entitlements.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entitlement = await response.parse()
+ assert_matches_type(OrgEntitlements, entitlement, 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.organization.entitlements.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entitlement = await response.parse()
+ assert_matches_type(OrgEntitlements, entitlement, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/organization/test_limits.py b/tests/api_resources/organization/test_limits.py
new file mode 100644
index 00000000..a9dca6d0
--- /dev/null
+++ b/tests/api_resources/organization/test_limits.py
@@ -0,0 +1,152 @@
+# 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.organization import OrgLimits
+
+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.organization.limits.retrieve()
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.organization.limits.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = response.parse()
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.organization.limits.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = response.parse()
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ limit = client.organization.limits.update()
+ assert_matches_type(OrgLimits, 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.organization.limits.update(
+ default_project_max_concurrent_sessions=0,
+ )
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.organization.limits.with_raw_response.update()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = response.parse()
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.organization.limits.with_streaming_response.update() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = response.parse()
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+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.organization.limits.retrieve()
+ assert_matches_type(OrgLimits, 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.organization.limits.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = await response.parse()
+ assert_matches_type(OrgLimits, 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.organization.limits.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = await response.parse()
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ limit = await async_client.organization.limits.update()
+ assert_matches_type(OrgLimits, 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.organization.limits.update(
+ default_project_max_concurrent_sessions=0,
+ )
+ assert_matches_type(OrgLimits, 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.organization.limits.with_raw_response.update()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ limit = await response.parse()
+ assert_matches_type(OrgLimits, 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.organization.limits.with_streaming_response.update() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ limit = await response.parse()
+ assert_matches_type(OrgLimits, limit, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/projects/test_limits.py b/tests/api_resources/projects/test_limits.py
index 9df6a0df..e8791c30 100644
--- a/tests/api_resources/projects/test_limits.py
+++ b/tests/api_resources/projects/test_limits.py
@@ -21,7 +21,7 @@ class TestLimits:
@parametrize
def test_method_retrieve(self, client: Kernel) -> None:
limit = client.projects.limits.retrieve(
- "id",
+ "id_or_name",
)
assert_matches_type(ProjectLimits, limit, path=["response"])
@@ -29,7 +29,7 @@ def test_method_retrieve(self, client: Kernel) -> None:
@parametrize
def test_raw_response_retrieve(self, client: Kernel) -> None:
response = client.projects.limits.with_raw_response.retrieve(
- "id",
+ "id_or_name",
)
assert response.is_closed is True
@@ -41,7 +41,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_retrieve(self, client: Kernel) -> None:
with client.projects.limits.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"
@@ -54,7 +54,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.projects.limits.with_raw_response.retrieve(
"",
)
@@ -63,7 +63,7 @@ def test_path_params_retrieve(self, client: Kernel) -> None:
@parametrize
def test_method_update(self, client: Kernel) -> None:
limit = client.projects.limits.update(
- id="id",
+ id_or_name="id_or_name",
)
assert_matches_type(ProjectLimits, limit, path=["response"])
@@ -71,10 +71,9 @@ def test_method_update(self, client: Kernel) -> None:
@parametrize
def test_method_update_with_all_params(self, client: Kernel) -> None:
limit = client.projects.limits.update(
- id="id",
+ id_or_name="id_or_name",
max_concurrent_invocations=0,
max_concurrent_sessions=0,
- max_persistent_sessions=0,
max_pooled_sessions=0,
)
assert_matches_type(ProjectLimits, limit, path=["response"])
@@ -83,7 +82,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_update(self, client: Kernel) -> None:
response = client.projects.limits.with_raw_response.update(
- id="id",
+ id_or_name="id_or_name",
)
assert response.is_closed is True
@@ -95,7 +94,7 @@ def test_raw_response_update(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_update(self, client: Kernel) -> None:
with client.projects.limits.with_streaming_response.update(
- 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"
@@ -108,9 +107,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.projects.limits.with_raw_response.update(
- id="",
+ id_or_name="",
)
@@ -123,7 +122,7 @@ class TestAsyncLimits:
@parametrize
async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
limit = await async_client.projects.limits.retrieve(
- "id",
+ "id_or_name",
)
assert_matches_type(ProjectLimits, limit, path=["response"])
@@ -131,7 +130,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.projects.limits.with_raw_response.retrieve(
- "id",
+ "id_or_name",
)
assert response.is_closed is True
@@ -143,7 +142,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.projects.limits.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"
@@ -156,7 +155,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.projects.limits.with_raw_response.retrieve(
"",
)
@@ -165,7 +164,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_update(self, async_client: AsyncKernel) -> None:
limit = await async_client.projects.limits.update(
- id="id",
+ id_or_name="id_or_name",
)
assert_matches_type(ProjectLimits, limit, path=["response"])
@@ -173,10 +172,9 @@ async def test_method_update(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
limit = await async_client.projects.limits.update(
- id="id",
+ id_or_name="id_or_name",
max_concurrent_invocations=0,
max_concurrent_sessions=0,
- max_persistent_sessions=0,
max_pooled_sessions=0,
)
assert_matches_type(ProjectLimits, limit, path=["response"])
@@ -185,7 +183,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
response = await async_client.projects.limits.with_raw_response.update(
- id="id",
+ id_or_name="id_or_name",
)
assert response.is_closed is True
@@ -197,7 +195,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.projects.limits.with_streaming_response.update(
- 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"
@@ -210,7 +208,7 @@ 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.projects.limits.with_raw_response.update(
- id="",
+ id_or_name="",
)
diff --git a/tests/api_resources/search/__init__.py b/tests/api_resources/search/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/search/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/search/test_contents.py b/tests/api_resources/search/test_contents.py
new file mode 100644
index 00000000..b4de0578
--- /dev/null
+++ b/tests/api_resources/search/test_contents.py
@@ -0,0 +1,152 @@
+# 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.search import Response
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestContents:
+ 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_fetch(self, client: Kernel) -> None:
+ content = client.search.contents.fetch(
+ id="srch_abc123",
+ )
+ assert_matches_type(Response, content, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_fetch_with_all_params(self, client: Kernel) -> None:
+ content = client.search.contents.fetch(
+ id="srch_abc123",
+ content={
+ "browser": {
+ "browser_id": "browser_id",
+ "mode": "curl",
+ },
+ "format": "markdown",
+ "max_age_hours": 0,
+ "max_chars": 100,
+ "source": "auto",
+ "timeout_ms": 1000,
+ },
+ limit=1,
+ result_ids=["string"],
+ timeout_ms=1000,
+ )
+ assert_matches_type(Response, content, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_fetch(self, client: Kernel) -> None:
+ response = client.search.contents.with_raw_response.fetch(
+ id="srch_abc123",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ content = response.parse()
+ assert_matches_type(Response, content, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_fetch(self, client: Kernel) -> None:
+ with client.search.contents.with_streaming_response.fetch(
+ id="srch_abc123",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ content = response.parse()
+ assert_matches_type(Response, content, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_fetch(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.search.contents.with_raw_response.fetch(
+ id="",
+ )
+
+
+class TestAsyncContents:
+ 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_fetch(self, async_client: AsyncKernel) -> None:
+ content = await async_client.search.contents.fetch(
+ id="srch_abc123",
+ )
+ assert_matches_type(Response, content, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_fetch_with_all_params(self, async_client: AsyncKernel) -> None:
+ content = await async_client.search.contents.fetch(
+ id="srch_abc123",
+ content={
+ "browser": {
+ "browser_id": "browser_id",
+ "mode": "curl",
+ },
+ "format": "markdown",
+ "max_age_hours": 0,
+ "max_chars": 100,
+ "source": "auto",
+ "timeout_ms": 1000,
+ },
+ limit=1,
+ result_ids=["string"],
+ timeout_ms=1000,
+ )
+ assert_matches_type(Response, content, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_fetch(self, async_client: AsyncKernel) -> None:
+ response = await async_client.search.contents.with_raw_response.fetch(
+ id="srch_abc123",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ content = await response.parse()
+ assert_matches_type(Response, content, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_fetch(self, async_client: AsyncKernel) -> None:
+ async with async_client.search.contents.with_streaming_response.fetch(
+ id="srch_abc123",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ content = await response.parse()
+ assert_matches_type(Response, content, 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_fetch(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.search.contents.with_raw_response.fetch(
+ id="",
+ )
diff --git a/tests/api_resources/search/test_providers.py b/tests/api_resources/search/test_providers.py
new file mode 100644
index 00000000..0f511099
--- /dev/null
+++ b/tests/api_resources/search/test_providers.py
@@ -0,0 +1,96 @@
+# 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.search import ProviderListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestProviders:
+ 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_list(self, client: Kernel) -> None:
+ provider = client.search.providers.list()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ provider = client.search.providers.list(
+ slug="brave",
+ )
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.search.providers.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ provider = response.parse()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.search.providers.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ provider = response.parse()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncProviders:
+ 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_list(self, async_client: AsyncKernel) -> None:
+ provider = await async_client.search.providers.list()
+ assert_matches_type(ProviderListResponse, provider, 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:
+ provider = await async_client.search.providers.list(
+ slug="brave",
+ )
+ assert_matches_type(ProviderListResponse, provider, 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.search.providers.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ provider = await response.parse()
+ assert_matches_type(ProviderListResponse, provider, 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.search.providers.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ provider = await response.parse()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/telemetry/__init__.py b/tests/api_resources/telemetry/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/telemetry/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/telemetry/test_destinations.py b/tests/api_resources/telemetry/test_destinations.py
new file mode 100644
index 00000000..ddf06c0d
--- /dev/null
+++ b/tests/api_resources/telemetry/test_destinations.py
@@ -0,0 +1,477 @@
+# 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.pagination import SyncOffsetPagination, AsyncOffsetPagination
+from kernel.types.telemetry import (
+ OtlpDestination,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestDestinations:
+ 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:
+ destination = client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ description="description",
+ headers={"foo": "string"},
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.create(
+ endpoint="x",
+ name="name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.create(
+ endpoint="x",
+ name="name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, 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:
+ destination = client.telemetry.destinations.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, 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_or_name` but received ''"):
+ client.telemetry.destinations.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ description="description",
+ endpoint="x",
+ headers={"foo": "string"},
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.update(
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.update(
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, 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_or_name` but received ''"):
+ client.telemetry.destinations.with_raw_response.update(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.list()
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, 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:
+ destination = client.telemetry.destinations.delete(
+ "id_or_name",
+ )
+ assert destination is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert destination is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert destination 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_or_name` but received ''"):
+ client.telemetry.destinations.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncDestinations:
+ 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:
+ destination = await async_client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ description="description",
+ headers={"foo": "string"},
+ )
+ assert_matches_type(OtlpDestination, destination, 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.telemetry.destinations.with_raw_response.create(
+ endpoint="x",
+ name="name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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.telemetry.destinations.with_streaming_response.create(
+ endpoint="x",
+ name="name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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:
+ destination = await async_client.telemetry.destinations.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, 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.telemetry.destinations.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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.telemetry.destinations.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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_or_name` but received ''"):
+ await async_client.telemetry.destinations.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, 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:
+ destination = await async_client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ description="description",
+ endpoint="x",
+ headers={"foo": "string"},
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, 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.telemetry.destinations.with_raw_response.update(
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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.telemetry.destinations.with_streaming_response.update(
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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_or_name` but received ''"):
+ await async_client.telemetry.destinations.with_raw_response.update(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.list()
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, 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:
+ destination = await async_client.telemetry.destinations.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, 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.telemetry.destinations.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, 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.telemetry.destinations.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, 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:
+ destination = await async_client.telemetry.destinations.delete(
+ "id_or_name",
+ )
+ assert destination 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.telemetry.destinations.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert destination 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.telemetry.destinations.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert destination 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_or_name` but received ''"):
+ await async_client.telemetry.destinations.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/test_api_keys.py b/tests/api_resources/test_api_keys.py
new file mode 100644
index 00000000..67005442
--- /dev/null
+++ b/tests/api_resources/test_api_keys.py
@@ -0,0 +1,603 @@
+# 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 (
+ APIKey,
+ CreatedAPIKey,
+)
+from kernel._utils import parse_datetime
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAPIKeys:
+ 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:
+ api_key = client.api_keys.create(
+ name="staging",
+ )
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Kernel) -> None:
+ api_key = client.api_keys.create(
+ name="staging",
+ days_to_expire=30,
+ expires_at=parse_datetime("2025-01-15T09:00:00Z"),
+ policy={
+ "statements": [
+ {
+ "action": "browsers:use",
+ "resource": "projects/proj_abc123/browsers/br_abc123",
+ }
+ ]
+ },
+ project_id="proj_abc123",
+ )
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Kernel) -> None:
+ response = client.api_keys.with_raw_response.create(
+ name="staging",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Kernel) -> None:
+ with client.api_keys.with_streaming_response.create(
+ name="staging",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, 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:
+ api_key = client.api_keys.retrieve(
+ id="id",
+ )
+ assert_matches_type(APIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve_with_all_params(self, client: Kernel) -> None:
+ api_key = client.api_keys.retrieve(
+ id="id",
+ include_deleted=True,
+ )
+ assert_matches_type(APIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.api_keys.with_raw_response.retrieve(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = response.parse()
+ assert_matches_type(APIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.api_keys.with_streaming_response.retrieve(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = response.parse()
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_raw_response.retrieve(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ api_key = client.api_keys.update(
+ id="id",
+ name="new-api-name",
+ )
+ assert_matches_type(APIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.api_keys.with_raw_response.update(
+ id="id",
+ name="new-api-name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = response.parse()
+ assert_matches_type(APIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.api_keys.with_streaming_response.update(
+ id="id",
+ name="new-api-name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = response.parse()
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_raw_response.update(
+ id="",
+ name="new-api-name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ api_key = client.api_keys.list()
+ assert_matches_type(SyncOffsetPagination[APIKey], api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ api_key = client.api_keys.list(
+ include_deleted=True,
+ limit=100,
+ name="name",
+ offset=0,
+ query="query",
+ sort_by="created_at",
+ sort_direction="asc",
+ status="active",
+ )
+ assert_matches_type(SyncOffsetPagination[APIKey], api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.api_keys.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = response.parse()
+ assert_matches_type(SyncOffsetPagination[APIKey], api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.api_keys.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = response.parse()
+ assert_matches_type(SyncOffsetPagination[APIKey], api_key, 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:
+ api_key = client.api_keys.delete(
+ "id",
+ )
+ assert api_key is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.api_keys.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = response.parse()
+ assert api_key is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.api_keys.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = response.parse()
+ assert api_key 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.api_keys.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_rotate(self, client: Kernel) -> None:
+ api_key = client.api_keys.rotate(
+ id="id",
+ )
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_rotate_with_all_params(self, client: Kernel) -> None:
+ api_key = client.api_keys.rotate(
+ id="id",
+ days_to_expire=30,
+ expire_in_days=7,
+ )
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_rotate(self, client: Kernel) -> None:
+ response = client.api_keys.with_raw_response.rotate(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_rotate(self, client: Kernel) -> None:
+ with client.api_keys.with_streaming_response.rotate(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_rotate(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.api_keys.with_raw_response.rotate(
+ id="",
+ )
+
+
+class TestAsyncAPIKeys:
+ 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:
+ api_key = await async_client.api_keys.create(
+ name="staging",
+ )
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None:
+ api_key = await async_client.api_keys.create(
+ name="staging",
+ days_to_expire=30,
+ expires_at=parse_datetime("2025-01-15T09:00:00Z"),
+ policy={
+ "statements": [
+ {
+ "action": "browsers:use",
+ "resource": "projects/proj_abc123/browsers/br_abc123",
+ }
+ ]
+ },
+ project_id="proj_abc123",
+ )
+ assert_matches_type(CreatedAPIKey, api_key, 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.api_keys.with_raw_response.create(
+ name="staging",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = await response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, 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.api_keys.with_streaming_response.create(
+ name="staging",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = await response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, 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:
+ api_key = await async_client.api_keys.retrieve(
+ id="id",
+ )
+ assert_matches_type(APIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve_with_all_params(self, async_client: AsyncKernel) -> None:
+ api_key = await async_client.api_keys.retrieve(
+ id="id",
+ include_deleted=True,
+ )
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_raw_response.retrieve(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = await response.parse()
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_streaming_response.retrieve(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = await response.parse()
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_raw_response.retrieve(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ api_key = await async_client.api_keys.update(
+ id="id",
+ name="new-api-name",
+ )
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_raw_response.update(
+ id="id",
+ name="new-api-name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = await response.parse()
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_streaming_response.update(
+ id="id",
+ name="new-api-name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = await response.parse()
+ assert_matches_type(APIKey, api_key, 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.api_keys.with_raw_response.update(
+ id="",
+ name="new-api-name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ api_key = await async_client.api_keys.list()
+ assert_matches_type(AsyncOffsetPagination[APIKey], api_key, 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:
+ api_key = await async_client.api_keys.list(
+ include_deleted=True,
+ limit=100,
+ name="name",
+ offset=0,
+ query="query",
+ sort_by="created_at",
+ sort_direction="asc",
+ status="active",
+ )
+ assert_matches_type(AsyncOffsetPagination[APIKey], api_key, 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.api_keys.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[APIKey], api_key, 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.api_keys.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[APIKey], api_key, 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:
+ api_key = await async_client.api_keys.delete(
+ "id",
+ )
+ assert api_key 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.api_keys.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = await response.parse()
+ assert api_key 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.api_keys.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = await response.parse()
+ assert api_key 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.api_keys.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_rotate(self, async_client: AsyncKernel) -> None:
+ api_key = await async_client.api_keys.rotate(
+ id="id",
+ )
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_rotate_with_all_params(self, async_client: AsyncKernel) -> None:
+ api_key = await async_client.api_keys.rotate(
+ id="id",
+ days_to_expire=30,
+ expire_in_days=7,
+ )
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_rotate(self, async_client: AsyncKernel) -> None:
+ response = await async_client.api_keys.with_raw_response.rotate(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ api_key = await response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_rotate(self, async_client: AsyncKernel) -> None:
+ async with async_client.api_keys.with_streaming_response.rotate(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ api_key = await response.parse()
+ assert_matches_type(CreatedAPIKey, api_key, 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_rotate(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.api_keys.with_raw_response.rotate(
+ id="",
+ )
diff --git a/tests/api_resources/test_audit_logs.py b/tests/api_resources/test_audit_logs.py
new file mode 100644
index 00000000..7a8ebe41
--- /dev/null
+++ b/tests/api_resources/test_audit_logs.py
@@ -0,0 +1,276 @@
+# 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 httpx
+import pytest
+from respx import MockRouter
+
+from kernel import Kernel, AsyncKernel
+from tests.utils import assert_matches_type
+from kernel.types import AuditLogEntry
+from kernel._utils import parse_datetime
+from kernel._response import (
+ BinaryAPIResponse,
+ AsyncBinaryAPIResponse,
+ StreamedBinaryAPIResponse,
+ AsyncStreamedBinaryAPIResponse,
+)
+from kernel.pagination import SyncPageTokenPagination, AsyncPageTokenPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAuditLogs:
+ 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_list(self, client: Kernel) -> None:
+ audit_log = client.audit_logs.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+ assert_matches_type(SyncPageTokenPagination[AuditLogEntry], audit_log, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ audit_log = client.audit_logs.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ auth_strategy="auth_strategy",
+ exclude_method=["string"],
+ limit=1,
+ method="method",
+ page_token="page_token",
+ search="search",
+ search_user_id=["string"],
+ service="service",
+ )
+ assert_matches_type(SyncPageTokenPagination[AuditLogEntry], audit_log, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.audit_logs.with_raw_response.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ audit_log = response.parse()
+ assert_matches_type(SyncPageTokenPagination[AuditLogEntry], audit_log, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.audit_logs.with_streaming_response.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ audit_log = response.parse()
+ assert_matches_type(SyncPageTokenPagination[AuditLogEntry], audit_log, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_method_export_chunk(self, client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ audit_log = client.audit_logs.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+ assert audit_log.is_closed
+ assert audit_log.json() == {"foo": "bar"}
+ assert cast(Any, audit_log.is_closed) is True
+ assert isinstance(audit_log, BinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_method_export_chunk_with_all_params(self, client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ audit_log = client.audit_logs.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ auth_strategy="auth_strategy",
+ cursor="cursor",
+ exclude_method=["string"],
+ format="jsonl",
+ limit=1,
+ method="method",
+ search="search",
+ search_user_id=["string"],
+ service="service",
+ )
+ assert audit_log.is_closed
+ assert audit_log.json() == {"foo": "bar"}
+ assert cast(Any, audit_log.is_closed) is True
+ assert isinstance(audit_log, BinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_raw_response_export_chunk(self, client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ audit_log = client.audit_logs.with_raw_response.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+
+ assert audit_log.is_closed is True
+ assert audit_log.http_request.headers.get("X-Stainless-Lang") == "python"
+ assert audit_log.json() == {"foo": "bar"}
+ assert isinstance(audit_log, BinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_streaming_response_export_chunk(self, client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ with client.audit_logs.with_streaming_response.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ ) as audit_log:
+ assert not audit_log.is_closed
+ assert audit_log.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ assert audit_log.json() == {"foo": "bar"}
+ assert cast(Any, audit_log.is_closed) is True
+ assert isinstance(audit_log, StreamedBinaryAPIResponse)
+
+ assert cast(Any, audit_log.is_closed) is True
+
+
+class TestAsyncAuditLogs:
+ 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_list(self, async_client: AsyncKernel) -> None:
+ audit_log = await async_client.audit_logs.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+ assert_matches_type(AsyncPageTokenPagination[AuditLogEntry], audit_log, 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:
+ audit_log = await async_client.audit_logs.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ auth_strategy="auth_strategy",
+ exclude_method=["string"],
+ limit=1,
+ method="method",
+ page_token="page_token",
+ search="search",
+ search_user_id=["string"],
+ service="service",
+ )
+ assert_matches_type(AsyncPageTokenPagination[AuditLogEntry], audit_log, 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.audit_logs.with_raw_response.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ audit_log = await response.parse()
+ assert_matches_type(AsyncPageTokenPagination[AuditLogEntry], audit_log, 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.audit_logs.with_streaming_response.list(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ audit_log = await response.parse()
+ assert_matches_type(AsyncPageTokenPagination[AuditLogEntry], audit_log, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_method_export_chunk(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ audit_log = await async_client.audit_logs.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+ assert audit_log.is_closed
+ assert await audit_log.json() == {"foo": "bar"}
+ assert cast(Any, audit_log.is_closed) is True
+ assert isinstance(audit_log, AsyncBinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_method_export_chunk_with_all_params(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ audit_log = await async_client.audit_logs.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ auth_strategy="auth_strategy",
+ cursor="cursor",
+ exclude_method=["string"],
+ format="jsonl",
+ limit=1,
+ method="method",
+ search="search",
+ search_user_id=["string"],
+ service="service",
+ )
+ assert audit_log.is_closed
+ assert await audit_log.json() == {"foo": "bar"}
+ assert cast(Any, audit_log.is_closed) is True
+ assert isinstance(audit_log, AsyncBinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_raw_response_export_chunk(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ audit_log = await async_client.audit_logs.with_raw_response.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ )
+
+ assert audit_log.is_closed is True
+ assert audit_log.http_request.headers.get("X-Stainless-Lang") == "python"
+ assert await audit_log.json() == {"foo": "bar"}
+ assert isinstance(audit_log, AsyncBinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_streaming_response_export_chunk(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ async with async_client.audit_logs.with_streaming_response.export_chunk(
+ end=parse_datetime("2026-01-02T00:00:00Z"),
+ start=parse_datetime("2026-01-01T00:00:00Z"),
+ ) as audit_log:
+ assert not audit_log.is_closed
+ assert audit_log.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ assert await audit_log.json() == {"foo": "bar"}
+ assert cast(Any, audit_log.is_closed) is True
+ assert isinstance(audit_log, AsyncStreamedBinaryAPIResponse)
+
+ assert cast(Any, audit_log.is_closed) is True
diff --git a/tests/api_resources/test_browser_pools.py b/tests/api_resources/test_browser_pools.py
index 959eeef2..04734119 100644
--- a/tests/api_resources/test_browser_pools.py
+++ b/tests/api_resources/test_browser_pools.py
@@ -11,9 +11,9 @@
from tests.utils import assert_matches_type
from kernel.types import (
BrowserPool,
- BrowserPoolListResponse,
BrowserPoolAcquireResponse,
)
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -44,16 +44,59 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
fill_rate_per_minute=0,
headless=False,
kiosk_mode=True,
+ memory="8GiB",
name="my-pool",
+ network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
profile={
"id": "id",
"name": "name",
- "save_changes": True,
},
proxy_id="proxy_id",
+ refresh_on_profile_update=True,
+ region="us-east",
start_url="https://example.com",
stealth=True,
- timeout_seconds=60,
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ timeout_seconds=10,
viewport={
"height": 800,
"width": 1280,
@@ -135,7 +178,6 @@ def test_path_params_retrieve(self, client: Kernel) -> None:
def test_method_update(self, client: Kernel) -> None:
browser_pool = client.browser_pools.update(
id_or_name="id_or_name",
- size=10,
)
assert_matches_type(BrowserPool, browser_pool, path=["response"])
@@ -144,9 +186,21 @@ def test_method_update(self, client: Kernel) -> None:
def test_method_update_with_all_params(self, client: Kernel) -> None:
browser_pool = client.browser_pools.update(
id_or_name="id_or_name",
- size=10,
chrome_policy={"foo": "bar"},
discard_all_idle=False,
+ expected_network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
extensions=[
{
"id": "id",
@@ -156,16 +210,59 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
fill_rate_per_minute=0,
headless=False,
kiosk_mode=True,
+ memory="1GiB",
name="my-pool",
+ network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
profile={
"id": "id",
"name": "name",
- "save_changes": True,
},
proxy_id="proxy_id",
+ refresh_on_profile_update=True,
+ size=10,
start_url="https://example.com",
stealth=True,
- timeout_seconds=60,
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ timeout_seconds=10,
viewport={
"height": 800,
"width": 1280,
@@ -179,7 +276,6 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
def test_raw_response_update(self, client: Kernel) -> None:
response = client.browser_pools.with_raw_response.update(
id_or_name="id_or_name",
- size=10,
)
assert response.is_closed is True
@@ -192,7 +288,6 @@ def test_raw_response_update(self, client: Kernel) -> None:
def test_streaming_response_update(self, client: Kernel) -> None:
with client.browser_pools.with_streaming_response.update(
id_or_name="id_or_name",
- size=10,
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -208,14 +303,25 @@ def test_path_params_update(self, client: Kernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.browser_pools.with_raw_response.update(
id_or_name="",
- size=10,
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_list(self, client: Kernel) -> None:
browser_pool = client.browser_pools.list()
- assert_matches_type(BrowserPoolListResponse, browser_pool, path=["response"])
+ assert_matches_type(SyncOffsetPagination[BrowserPool], browser_pool, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ browser_pool = client.browser_pools.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ region="us-east",
+ )
+ assert_matches_type(SyncOffsetPagination[BrowserPool], browser_pool, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -225,7 +331,7 @@ def test_raw_response_list(self, client: Kernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
browser_pool = response.parse()
- assert_matches_type(BrowserPoolListResponse, browser_pool, path=["response"])
+ assert_matches_type(SyncOffsetPagination[BrowserPool], browser_pool, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -235,7 +341,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
browser_pool = response.parse()
- assert_matches_type(BrowserPoolListResponse, browser_pool, path=["response"])
+ assert_matches_type(SyncOffsetPagination[BrowserPool], browser_pool, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -304,6 +410,45 @@ def test_method_acquire_with_all_params(self, client: Kernel) -> None:
browser_pool = client.browser_pools.acquire(
id_or_name="id_or_name",
acquire_timeout_seconds=0,
+ name="checkout-flow-1",
+ profile={
+ "id": "id",
+ "name": "name",
+ "save_changes": True,
+ },
+ start_url="https://example.com",
+ tags={
+ "team": "backend",
+ "env": "staging",
+ },
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
)
assert_matches_type(BrowserPoolAcquireResponse, browser_pool, path=["response"])
@@ -468,16 +613,59 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
fill_rate_per_minute=0,
headless=False,
kiosk_mode=True,
+ memory="8GiB",
name="my-pool",
+ network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
profile={
"id": "id",
"name": "name",
- "save_changes": True,
},
proxy_id="proxy_id",
+ refresh_on_profile_update=True,
+ region="us-east",
start_url="https://example.com",
stealth=True,
- timeout_seconds=60,
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ timeout_seconds=10,
viewport={
"height": 800,
"width": 1280,
@@ -559,7 +747,6 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
async def test_method_update(self, async_client: AsyncKernel) -> None:
browser_pool = await async_client.browser_pools.update(
id_or_name="id_or_name",
- size=10,
)
assert_matches_type(BrowserPool, browser_pool, path=["response"])
@@ -568,9 +755,21 @@ async def test_method_update(self, async_client: AsyncKernel) -> None:
async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
browser_pool = await async_client.browser_pools.update(
id_or_name="id_or_name",
- size=10,
chrome_policy={"foo": "bar"},
discard_all_idle=False,
+ expected_network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
extensions=[
{
"id": "id",
@@ -580,16 +779,59 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
fill_rate_per_minute=0,
headless=False,
kiosk_mode=True,
+ memory="1GiB",
name="my-pool",
+ network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
profile={
"id": "id",
"name": "name",
- "save_changes": True,
},
proxy_id="proxy_id",
+ refresh_on_profile_update=True,
+ size=10,
start_url="https://example.com",
stealth=True,
- timeout_seconds=60,
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
+ timeout_seconds=10,
viewport={
"height": 800,
"width": 1280,
@@ -603,7 +845,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
response = await async_client.browser_pools.with_raw_response.update(
id_or_name="id_or_name",
- size=10,
)
assert response.is_closed is True
@@ -616,7 +857,6 @@ async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
async def test_streaming_response_update(self, async_client: AsyncKernel) -> None:
async with async_client.browser_pools.with_streaming_response.update(
id_or_name="id_or_name",
- size=10,
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -632,14 +872,25 @@ async def test_path_params_update(self, async_client: AsyncKernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.browser_pools.with_raw_response.update(
id_or_name="",
- size=10,
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
browser_pool = await async_client.browser_pools.list()
- assert_matches_type(BrowserPoolListResponse, browser_pool, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[BrowserPool], browser_pool, 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:
+ browser_pool = await async_client.browser_pools.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ region="us-east",
+ )
+ assert_matches_type(AsyncOffsetPagination[BrowserPool], browser_pool, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -649,7 +900,7 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
browser_pool = await response.parse()
- assert_matches_type(BrowserPoolListResponse, browser_pool, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[BrowserPool], browser_pool, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -659,7 +910,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
browser_pool = await response.parse()
- assert_matches_type(BrowserPoolListResponse, browser_pool, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[BrowserPool], browser_pool, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -728,6 +979,45 @@ async def test_method_acquire_with_all_params(self, async_client: AsyncKernel) -
browser_pool = await async_client.browser_pools.acquire(
id_or_name="id_or_name",
acquire_timeout_seconds=0,
+ name="checkout-flow-1",
+ profile={
+ "id": "id",
+ "name": "name",
+ "save_changes": True,
+ },
+ start_url="https://example.com",
+ tags={
+ "team": "backend",
+ "env": "staging",
+ },
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
)
assert_matches_type(BrowserPoolAcquireResponse, browser_pool, path=["response"])
diff --git a/tests/api_resources/test_browsers.py b/tests/api_resources/test_browsers.py
index 94c52654..6c5e9696 100644
--- a/tests/api_resources/test_browsers.py
+++ b/tests/api_resources/test_browsers.py
@@ -10,6 +10,7 @@
from kernel import Kernel, AsyncKernel
from tests.utils import assert_matches_type
from kernel.types import (
+ BrowserReplResult,
BrowserCurlResponse,
BrowserListResponse,
BrowserCreateResponse,
@@ -18,8 +19,6 @@
)
from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
-# pyright: reportDeprecated=false
-
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -36,6 +35,7 @@ def test_method_create(self, client: Kernel) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Kernel) -> None:
browser = client.browsers.create(
+ chrome_policy={"foo": "bar"},
extensions=[
{
"id": "id",
@@ -46,16 +46,79 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
headless=False,
invocation_id="rr33xuugxj9h0bkf1rdt2bet",
kiosk_mode=True,
- persistence={"id": "my-awesome-browser-for-user-1234"},
+ location={
+ "locale": "en-US",
+ "timezone": "America/Los_Angeles",
+ },
+ memory="8GiB",
+ name="checkout-flow-1",
+ network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
profile={
"id": "id",
"name": "name",
"save_changes": True,
},
+ proxy={
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
proxy_id="proxy_id",
+ region="us-east",
start_url="https://example.com",
stealth=True,
+ tags={
+ "team": "backend",
+ "env": "staging",
+ },
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
timeout_seconds=10,
+ vaults=[
+ {
+ "id": "id",
+ "name": "x",
+ }
+ ],
+ video_memory="2GiB",
viewport={
"height": 800,
"width": 1280,
@@ -90,7 +153,7 @@ def test_streaming_response_create(self, client: Kernel) -> None:
@parametrize
def test_method_retrieve(self, client: Kernel) -> None:
browser = client.browsers.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(BrowserRetrieveResponse, browser, path=["response"])
@@ -98,7 +161,7 @@ def test_method_retrieve(self, client: Kernel) -> None:
@parametrize
def test_method_retrieve_with_all_params(self, client: Kernel) -> None:
browser = client.browsers.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
include_deleted=True,
)
assert_matches_type(BrowserRetrieveResponse, browser, path=["response"])
@@ -107,7 +170,7 @@ def test_method_retrieve_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_retrieve(self, client: Kernel) -> None:
response = client.browsers.with_raw_response.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -119,7 +182,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_retrieve(self, client: Kernel) -> None:
with client.browsers.with_streaming_response.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -132,16 +195,16 @@ 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.browsers.with_raw_response.retrieve(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_update(self, client: Kernel) -> None:
browser = client.browsers.update(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(BrowserUpdateResponse, browser, path=["response"])
@@ -149,14 +212,62 @@ def test_method_update(self, client: Kernel) -> None:
@parametrize
def test_method_update_with_all_params(self, client: Kernel) -> None:
browser = client.browsers.update(
- id="htzv5orfit78e1m2biiifpbv",
+ 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"],
+ "expected_allowlist_revision": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "mutation_id": "mutation_id",
+ },
profile={
"id": "id",
"name": "name",
"save_changes": True,
},
+ proxy={
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
proxy_id="proxy_id",
+ start_url="https://example.com",
+ tags={
+ "team": "backend",
+ "env": "staging",
+ },
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
viewport={
"height": 800,
"width": 1280,
@@ -170,7 +281,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_update(self, client: Kernel) -> None:
response = client.browsers.with_raw_response.update(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -182,7 +293,7 @@ def test_raw_response_update(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_update(self, client: Kernel) -> None:
with client.browsers.with_streaming_response.update(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -195,9 +306,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.browsers.with_raw_response.update(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -214,7 +325,9 @@ def test_method_list_with_all_params(self, client: Kernel) -> None:
limit=1,
offset=0,
query="query",
+ region="us-east",
status="active",
+ tags={"foo": "string"},
)
assert_matches_type(SyncOffsetPagination[BrowserListResponse], browser, path=["response"])
@@ -240,49 +353,11 @@ def test_streaming_response_list(self, client: Kernel) -> None:
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:
- with pytest.warns(DeprecationWarning):
- browser = client.browsers.delete(
- persistent_id="persistent_id",
- )
-
- assert browser is None
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_raw_response_delete(self, client: Kernel) -> None:
- with pytest.warns(DeprecationWarning):
- response = client.browsers.with_raw_response.delete(
- persistent_id="persistent_id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- browser = response.parse()
- assert browser is None
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- def test_streaming_response_delete(self, client: Kernel) -> None:
- with pytest.warns(DeprecationWarning):
- with client.browsers.with_streaming_response.delete(
- persistent_id="persistent_id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- browser = response.parse()
- assert browser is None
-
- assert cast(Any, response.is_closed) is True
-
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_curl(self, client: Kernel) -> None:
browser = client.browsers.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
)
assert_matches_type(BrowserCurlResponse, browser, path=["response"])
@@ -291,7 +366,7 @@ def test_method_curl(self, client: Kernel) -> None:
@parametrize
def test_method_curl_with_all_params(self, client: Kernel) -> None:
browser = client.browsers.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
body="body",
headers={"foo": "string"},
@@ -305,7 +380,7 @@ def test_method_curl_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_curl(self, client: Kernel) -> None:
response = client.browsers.with_raw_response.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
)
@@ -318,7 +393,7 @@ def test_raw_response_curl(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_curl(self, client: Kernel) -> None:
with client.browsers.with_streaming_response.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
) as response:
assert not response.is_closed
@@ -332,9 +407,9 @@ def test_streaming_response_curl(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_curl(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.browsers.with_raw_response.curl(
- id="",
+ id_or_name="",
url="url",
)
@@ -375,7 +450,7 @@ def test_streaming_response_delete_by_id(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_delete_by_id(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.browsers.with_raw_response.delete_by_id(
"",
)
@@ -384,7 +459,7 @@ def test_path_params_delete_by_id(self, client: Kernel) -> None:
@parametrize
def test_method_load_extensions(self, client: Kernel) -> None:
browser = client.browsers.load_extensions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
extensions=[
{
"name": "name",
@@ -398,7 +473,7 @@ def test_method_load_extensions(self, client: Kernel) -> None:
@parametrize
def test_raw_response_load_extensions(self, client: Kernel) -> None:
response = client.browsers.with_raw_response.load_extensions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
extensions=[
{
"name": "name",
@@ -416,7 +491,7 @@ def test_raw_response_load_extensions(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_load_extensions(self, client: Kernel) -> None:
with client.browsers.with_streaming_response.load_extensions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
extensions=[
{
"name": "name",
@@ -435,9 +510,9 @@ def test_streaming_response_load_extensions(self, client: Kernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_load_extensions(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.browsers.with_raw_response.load_extensions(
- id="",
+ id_or_name="",
extensions=[
{
"name": "name",
@@ -446,6 +521,63 @@ def test_path_params_load_extensions(self, client: Kernel) -> None:
],
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_repl(self, client: Kernel) -> None:
+ browser = client.browsers.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ )
+ assert_matches_type(BrowserReplResult, browser, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_repl_with_all_params(self, client: Kernel) -> None:
+ browser = client.browsers.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ reset=True,
+ timeout_sec=1,
+ )
+ assert_matches_type(BrowserReplResult, browser, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_repl(self, client: Kernel) -> None:
+ response = client.browsers.with_raw_response.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ browser = response.parse()
+ assert_matches_type(BrowserReplResult, browser, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_repl(self, client: Kernel) -> None:
+ with client.browsers.with_streaming_response.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ browser = response.parse()
+ assert_matches_type(BrowserReplResult, browser, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_repl(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.with_raw_response.repl(
+ id_or_name="",
+ code="code",
+ )
+
class TestAsyncBrowsers:
parametrize = pytest.mark.parametrize(
@@ -462,6 +594,7 @@ async def test_method_create(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.create(
+ chrome_policy={"foo": "bar"},
extensions=[
{
"id": "id",
@@ -472,16 +605,79 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
headless=False,
invocation_id="rr33xuugxj9h0bkf1rdt2bet",
kiosk_mode=True,
- persistence={"id": "my-awesome-browser-for-user-1234"},
+ location={
+ "locale": "en-US",
+ "timezone": "America/Los_Angeles",
+ },
+ memory="8GiB",
+ name="checkout-flow-1",
+ network={
+ "allowed_hosts": ["example.com", "*.example.com"],
+ "private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
+ "proxy_routes": [
+ {
+ "hosts": ["string"],
+ "proxy": {
+ "id": "x",
+ "name": "x",
+ },
+ }
+ ],
+ },
profile={
"id": "id",
"name": "name",
"save_changes": True,
},
+ proxy={
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
proxy_id="proxy_id",
+ region="us-east",
start_url="https://example.com",
stealth=True,
+ tags={
+ "team": "backend",
+ "env": "staging",
+ },
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
timeout_seconds=10,
+ vaults=[
+ {
+ "id": "id",
+ "name": "x",
+ }
+ ],
+ video_memory="2GiB",
viewport={
"height": 800,
"width": 1280,
@@ -516,7 +712,7 @@ async def test_streaming_response_create(self, async_client: AsyncKernel) -> Non
@parametrize
async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(BrowserRetrieveResponse, browser, path=["response"])
@@ -524,7 +720,7 @@ async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_retrieve_with_all_params(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
include_deleted=True,
)
assert_matches_type(BrowserRetrieveResponse, browser, path=["response"])
@@ -533,7 +729,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncKernel)
@parametrize
async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.with_raw_response.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -545,7 +741,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.browsers.with_streaming_response.retrieve(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -558,16 +754,16 @@ 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.browsers.with_raw_response.retrieve(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_update(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.update(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert_matches_type(BrowserUpdateResponse, browser, path=["response"])
@@ -575,14 +771,62 @@ async def test_method_update(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.update(
- id="htzv5orfit78e1m2biiifpbv",
+ 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"],
+ "expected_allowlist_revision": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "mutation_id": "mutation_id",
+ },
profile={
"id": "id",
"name": "name",
"save_changes": True,
},
+ proxy={
+ "id": "x",
+ "mode": "direct",
+ "name": "x",
+ },
proxy_id="proxy_id",
+ start_url="https://example.com",
+ tags={
+ "team": "backend",
+ "env": "staging",
+ },
+ telemetry={
+ "browser": {
+ "captcha": {"enabled": True},
+ "connection": {"enabled": True},
+ "console": {"enabled": True},
+ "control": {
+ "cdp": {"excluded_methods": ["Input.dispatchMouseEvent"]},
+ "enabled": True,
+ },
+ "interaction": {"enabled": True},
+ "network": {"enabled": True},
+ "page": {"enabled": True},
+ "platform": {"enabled": True},
+ "screenshot": {"enabled": True},
+ "system": {"enabled": True},
+ },
+ "enabled": True,
+ "export": {
+ "otlp": {
+ "destination": {
+ "id": "id",
+ "name": "name",
+ },
+ "enabled": True,
+ }
+ },
+ "storage": {"enabled": True},
+ },
viewport={
"height": 800,
"width": 1280,
@@ -596,7 +840,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.with_raw_response.update(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
)
assert response.is_closed is True
@@ -608,7 +852,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.browsers.with_streaming_response.update(
- id="htzv5orfit78e1m2biiifpbv",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -621,9 +865,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.browsers.with_raw_response.update(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -640,7 +884,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> N
limit=1,
offset=0,
query="query",
+ region="us-east",
status="active",
+ tags={"foo": "string"},
)
assert_matches_type(AsyncOffsetPagination[BrowserListResponse], browser, path=["response"])
@@ -666,49 +912,11 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
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:
- with pytest.warns(DeprecationWarning):
- browser = await async_client.browsers.delete(
- persistent_id="persistent_id",
- )
-
- assert browser is None
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_raw_response_delete(self, async_client: AsyncKernel) -> None:
- with pytest.warns(DeprecationWarning):
- response = await async_client.browsers.with_raw_response.delete(
- persistent_id="persistent_id",
- )
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- browser = await response.parse()
- assert browser is None
-
- @pytest.mark.skip(reason="Mock server tests are disabled")
- @parametrize
- async def test_streaming_response_delete(self, async_client: AsyncKernel) -> None:
- with pytest.warns(DeprecationWarning):
- async with async_client.browsers.with_streaming_response.delete(
- persistent_id="persistent_id",
- ) as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- browser = await response.parse()
- assert browser is None
-
- assert cast(Any, response.is_closed) is True
-
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_curl(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
)
assert_matches_type(BrowserCurlResponse, browser, path=["response"])
@@ -717,7 +925,7 @@ async def test_method_curl(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_curl_with_all_params(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
body="body",
headers={"foo": "string"},
@@ -731,7 +939,7 @@ async def test_method_curl_with_all_params(self, async_client: AsyncKernel) -> N
@parametrize
async def test_raw_response_curl(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.with_raw_response.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
)
@@ -744,7 +952,7 @@ async def test_raw_response_curl(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_curl(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.with_streaming_response.curl(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
url="url",
) as response:
assert not response.is_closed
@@ -758,9 +966,9 @@ async def test_streaming_response_curl(self, async_client: AsyncKernel) -> None:
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_curl(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.browsers.with_raw_response.curl(
- id="",
+ id_or_name="",
url="url",
)
@@ -801,7 +1009,7 @@ async def test_streaming_response_delete_by_id(self, async_client: AsyncKernel)
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_delete_by_id(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.browsers.with_raw_response.delete_by_id(
"",
)
@@ -810,7 +1018,7 @@ async def test_path_params_delete_by_id(self, async_client: AsyncKernel) -> None
@parametrize
async def test_method_load_extensions(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.load_extensions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
extensions=[
{
"name": "name",
@@ -824,7 +1032,7 @@ async def test_method_load_extensions(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_raw_response_load_extensions(self, async_client: AsyncKernel) -> None:
response = await async_client.browsers.with_raw_response.load_extensions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
extensions=[
{
"name": "name",
@@ -842,7 +1050,7 @@ async def test_raw_response_load_extensions(self, async_client: AsyncKernel) ->
@parametrize
async def test_streaming_response_load_extensions(self, async_client: AsyncKernel) -> None:
async with async_client.browsers.with_streaming_response.load_extensions(
- id="id",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
extensions=[
{
"name": "name",
@@ -861,9 +1069,9 @@ async def test_streaming_response_load_extensions(self, async_client: AsyncKerne
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_load_extensions(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.browsers.with_raw_response.load_extensions(
- id="",
+ id_or_name="",
extensions=[
{
"name": "name",
@@ -871,3 +1079,60 @@ async def test_path_params_load_extensions(self, async_client: AsyncKernel) -> N
}
],
)
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_repl(self, async_client: AsyncKernel) -> None:
+ browser = await async_client.browsers.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ )
+ assert_matches_type(BrowserReplResult, browser, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_repl_with_all_params(self, async_client: AsyncKernel) -> None:
+ browser = await async_client.browsers.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ reset=True,
+ timeout_sec=1,
+ )
+ assert_matches_type(BrowserReplResult, browser, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_repl(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.with_raw_response.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ browser = await response.parse()
+ assert_matches_type(BrowserReplResult, browser, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_repl(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.with_streaming_response.repl(
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ code="code",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ browser = await response.parse()
+ assert_matches_type(BrowserReplResult, browser, 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_repl(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.with_raw_response.repl(
+ id_or_name="",
+ code="code",
+ )
diff --git a/tests/api_resources/test_config_registry.py b/tests/api_resources/test_config_registry.py
new file mode 100644
index 00000000..18a3d598
--- /dev/null
+++ b/tests/api_resources/test_config_registry.py
@@ -0,0 +1,283 @@
+# 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 (
+ LookupResponse,
+ RecommendationSummary,
+ ConfigRegistryResponse,
+)
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestConfigRegistry:
+ 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_list(self, client: Kernel) -> None:
+ config_registry = client.config_registry.list()
+ assert_matches_type(SyncOffsetPagination[RecommendationSummary], config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ config_registry = client.config_registry.list(
+ limit=1,
+ offset=0,
+ search="search",
+ sort_by="target",
+ sort_order="asc",
+ )
+ assert_matches_type(SyncOffsetPagination[RecommendationSummary], config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.config_registry.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config_registry = response.parse()
+ assert_matches_type(SyncOffsetPagination[RecommendationSummary], config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.config_registry.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config_registry = response.parse()
+ assert_matches_type(SyncOffsetPagination[RecommendationSummary], config_registry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_lookup(self, client: Kernel) -> None:
+ config_registry = client.config_registry.lookup(
+ url="https://example.com",
+ )
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_lookup_with_all_params(self, client: Kernel) -> None:
+ config_registry = client.config_registry.lookup(
+ url="https://example.com",
+ allowed_proxy_countries=["US"],
+ )
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_lookup(self, client: Kernel) -> None:
+ response = client.config_registry.with_raw_response.lookup(
+ url="https://example.com",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config_registry = response.parse()
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_lookup(self, client: Kernel) -> None:
+ with client.config_registry.with_streaming_response.lookup(
+ url="https://example.com",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config_registry = response.parse()
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_resolve(self, client: Kernel) -> None:
+ config_registry = client.config_registry.resolve(
+ url="https://example.com",
+ )
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_resolve_with_all_params(self, client: Kernel) -> None:
+ config_registry = client.config_registry.resolve(
+ url="https://example.com",
+ allowed_proxy_countries=["US"],
+ intent="search for a black hoodie and add it to the cart",
+ )
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_resolve(self, client: Kernel) -> None:
+ response = client.config_registry.with_raw_response.resolve(
+ url="https://example.com",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config_registry = response.parse()
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_resolve(self, client: Kernel) -> None:
+ with client.config_registry.with_streaming_response.resolve(
+ url="https://example.com",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config_registry = response.parse()
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncConfigRegistry:
+ 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_list(self, async_client: AsyncKernel) -> None:
+ config_registry = await async_client.config_registry.list()
+ assert_matches_type(AsyncOffsetPagination[RecommendationSummary], config_registry, 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:
+ config_registry = await async_client.config_registry.list(
+ limit=1,
+ offset=0,
+ search="search",
+ sort_by="target",
+ sort_order="asc",
+ )
+ assert_matches_type(AsyncOffsetPagination[RecommendationSummary], config_registry, 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.config_registry.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config_registry = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[RecommendationSummary], config_registry, 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.config_registry.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config_registry = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[RecommendationSummary], config_registry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_lookup(self, async_client: AsyncKernel) -> None:
+ config_registry = await async_client.config_registry.lookup(
+ url="https://example.com",
+ )
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_lookup_with_all_params(self, async_client: AsyncKernel) -> None:
+ config_registry = await async_client.config_registry.lookup(
+ url="https://example.com",
+ allowed_proxy_countries=["US"],
+ )
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_lookup(self, async_client: AsyncKernel) -> None:
+ response = await async_client.config_registry.with_raw_response.lookup(
+ url="https://example.com",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config_registry = await response.parse()
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_lookup(self, async_client: AsyncKernel) -> None:
+ async with async_client.config_registry.with_streaming_response.lookup(
+ url="https://example.com",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config_registry = await response.parse()
+ assert_matches_type(LookupResponse, config_registry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_resolve(self, async_client: AsyncKernel) -> None:
+ config_registry = await async_client.config_registry.resolve(
+ url="https://example.com",
+ )
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_resolve_with_all_params(self, async_client: AsyncKernel) -> None:
+ config_registry = await async_client.config_registry.resolve(
+ url="https://example.com",
+ allowed_proxy_countries=["US"],
+ intent="search for a black hoodie and add it to the cart",
+ )
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_resolve(self, async_client: AsyncKernel) -> None:
+ response = await async_client.config_registry.with_raw_response.resolve(
+ url="https://example.com",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config_registry = await response.parse()
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_resolve(self, async_client: AsyncKernel) -> None:
+ async with async_client.config_registry.with_streaming_response.resolve(
+ url="https://example.com",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config_registry = await response.parse()
+ assert_matches_type(ConfigRegistryResponse, config_registry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_credential_providers.py b/tests/api_resources/test_credential_providers.py
index 93f44145..a47d76bb 100644
--- a/tests/api_resources/test_credential_providers.py
+++ b/tests/api_resources/test_credential_providers.py
@@ -12,9 +12,9 @@
from kernel.types import (
CredentialProvider,
CredentialProviderTestResult,
- CredentialProviderListResponse,
CredentialProviderListItemsResponse,
)
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -174,7 +174,17 @@ def test_path_params_update(self, client: Kernel) -> None:
@parametrize
def test_method_list(self, client: Kernel) -> None:
credential_provider = client.credential_providers.list()
- assert_matches_type(CredentialProviderListResponse, credential_provider, path=["response"])
+ assert_matches_type(SyncOffsetPagination[CredentialProvider], credential_provider, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ credential_provider = client.credential_providers.list(
+ limit=1,
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(SyncOffsetPagination[CredentialProvider], credential_provider, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -184,7 +194,7 @@ def test_raw_response_list(self, client: Kernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential_provider = response.parse()
- assert_matches_type(CredentialProviderListResponse, credential_provider, path=["response"])
+ assert_matches_type(SyncOffsetPagination[CredentialProvider], credential_provider, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -194,7 +204,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential_provider = response.parse()
- assert_matches_type(CredentialProviderListResponse, credential_provider, path=["response"])
+ assert_matches_type(SyncOffsetPagination[CredentialProvider], credential_provider, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -482,7 +492,17 @@ async def test_path_params_update(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
credential_provider = await async_client.credential_providers.list()
- assert_matches_type(CredentialProviderListResponse, credential_provider, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[CredentialProvider], credential_provider, 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:
+ credential_provider = await async_client.credential_providers.list(
+ limit=1,
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(AsyncOffsetPagination[CredentialProvider], credential_provider, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -492,7 +512,7 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential_provider = await response.parse()
- assert_matches_type(CredentialProviderListResponse, credential_provider, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[CredentialProvider], credential_provider, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -502,7 +522,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential_provider = await response.parse()
- assert_matches_type(CredentialProviderListResponse, credential_provider, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[CredentialProvider], credential_provider, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_credentials.py b/tests/api_resources/test_credentials.py
index ff932bef..b2079b5b 100644
--- a/tests/api_resources/test_credentials.py
+++ b/tests/api_resources/test_credentials.py
@@ -45,6 +45,9 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
"password": "mysecretpassword",
},
sso_provider="google",
+ totp_algorithm="SHA1",
+ totp_digits=6,
+ totp_period=30,
totp_secret="JBSWY3DPEHPK3PXP",
)
assert_matches_type(Credential, credential, path=["response"])
@@ -141,7 +144,11 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
credential = client.credentials.update(
id_or_name="id_or_name",
name="my-updated-login",
+ remove_value_keys=["old_field"],
sso_provider="google",
+ totp_algorithm="SHA1",
+ totp_digits=6,
+ totp_period=30,
totp_secret="JBSWY3DPEHPK3PXP",
values={
"username": "user@example.com",
@@ -197,6 +204,7 @@ def test_method_list_with_all_params(self, client: Kernel) -> None:
domain="domain",
limit=100,
offset=0,
+ query="query",
)
assert_matches_type(SyncOffsetPagination[Credential], credential, path=["response"])
@@ -336,6 +344,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
"password": "mysecretpassword",
},
sso_provider="google",
+ totp_algorithm="SHA1",
+ totp_digits=6,
+ totp_period=30,
totp_secret="JBSWY3DPEHPK3PXP",
)
assert_matches_type(Credential, credential, path=["response"])
@@ -432,7 +443,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
credential = await async_client.credentials.update(
id_or_name="id_or_name",
name="my-updated-login",
+ remove_value_keys=["old_field"],
sso_provider="google",
+ totp_algorithm="SHA1",
+ totp_digits=6,
+ totp_period=30,
totp_secret="JBSWY3DPEHPK3PXP",
values={
"username": "user@example.com",
@@ -488,6 +503,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> N
domain="domain",
limit=100,
offset=0,
+ query="query",
)
assert_matches_type(AsyncOffsetPagination[Credential], credential, path=["response"])
diff --git a/tests/api_resources/test_deployments.py b/tests/api_resources/test_deployments.py
index ed562864..4f619015 100644
--- a/tests/api_resources/test_deployments.py
+++ b/tests/api_resources/test_deployments.py
@@ -130,6 +130,7 @@ def test_method_list_with_all_params(self, client: Kernel) -> None:
app_version="app_version",
limit=1,
offset=0,
+ query="query",
)
assert_matches_type(SyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
@@ -361,6 +362,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> N
app_version="app_version",
limit=1,
offset=0,
+ query="query",
)
assert_matches_type(AsyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
diff --git a/tests/api_resources/test_extensions.py b/tests/api_resources/test_extensions.py
index 4a28fb5e..ace8ec6b 100644
--- a/tests/api_resources/test_extensions.py
+++ b/tests/api_resources/test_extensions.py
@@ -12,6 +12,7 @@
from kernel import Kernel, AsyncKernel
from tests.utils import assert_matches_type
from kernel.types import (
+ ExtensionGetResponse,
ExtensionListResponse,
ExtensionUploadResponse,
)
@@ -21,6 +22,7 @@
StreamedBinaryAPIResponse,
AsyncStreamedBinaryAPIResponse,
)
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -32,7 +34,18 @@ class TestExtensions:
@parametrize
def test_method_list(self, client: Kernel) -> None:
extension = client.extensions.list()
- assert_matches_type(ExtensionListResponse, extension, path=["response"])
+ assert_matches_type(SyncOffsetPagination[ExtensionListResponse], extension, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ extension = client.extensions.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(SyncOffsetPagination[ExtensionListResponse], extension, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -42,7 +55,7 @@ def test_raw_response_list(self, client: Kernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
extension = response.parse()
- assert_matches_type(ExtensionListResponse, extension, path=["response"])
+ assert_matches_type(SyncOffsetPagination[ExtensionListResponse], extension, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -52,7 +65,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
extension = response.parse()
- assert_matches_type(ExtensionListResponse, extension, path=["response"])
+ assert_matches_type(SyncOffsetPagination[ExtensionListResponse], extension, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -203,6 +216,48 @@ def test_streaming_response_download_from_chrome_store(self, client: Kernel, res
assert cast(Any, extension.is_closed) is True
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get(self, client: Kernel) -> None:
+ extension = client.extensions.get(
+ "id_or_name",
+ )
+ assert_matches_type(ExtensionGetResponse, extension, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_get(self, client: Kernel) -> None:
+ response = client.extensions.with_raw_response.get(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ extension = response.parse()
+ assert_matches_type(ExtensionGetResponse, extension, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_get(self, client: Kernel) -> None:
+ with client.extensions.with_streaming_response.get(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ extension = response.parse()
+ assert_matches_type(ExtensionGetResponse, extension, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_get(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.extensions.with_raw_response.get(
+ "",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_upload(self, client: Kernel) -> None:
@@ -256,7 +311,18 @@ class TestAsyncExtensions:
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
extension = await async_client.extensions.list()
- assert_matches_type(ExtensionListResponse, extension, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[ExtensionListResponse], extension, 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:
+ extension = await async_client.extensions.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(AsyncOffsetPagination[ExtensionListResponse], extension, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -266,7 +332,7 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
extension = await response.parse()
- assert_matches_type(ExtensionListResponse, extension, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[ExtensionListResponse], extension, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -276,7 +342,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
extension = await response.parse()
- assert_matches_type(ExtensionListResponse, extension, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[ExtensionListResponse], extension, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -433,6 +499,48 @@ async def test_streaming_response_download_from_chrome_store(
assert cast(Any, extension.is_closed) is True
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get(self, async_client: AsyncKernel) -> None:
+ extension = await async_client.extensions.get(
+ "id_or_name",
+ )
+ assert_matches_type(ExtensionGetResponse, extension, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncKernel) -> None:
+ response = await async_client.extensions.with_raw_response.get(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ extension = await response.parse()
+ assert_matches_type(ExtensionGetResponse, extension, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncKernel) -> None:
+ async with async_client.extensions.with_streaming_response.get(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ extension = await response.parse()
+ assert_matches_type(ExtensionGetResponse, extension, 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_get(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.extensions.with_raw_response.get(
+ "",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_upload(self, async_client: AsyncKernel) -> None:
diff --git a/tests/api_resources/test_invocations.py b/tests/api_resources/test_invocations.py
index d2784c21..2617629e 100644
--- a/tests/api_resources/test_invocations.py
+++ b/tests/api_resources/test_invocations.py
@@ -190,6 +190,7 @@ def test_method_list_with_all_params(self, client: Kernel) -> None:
deployment_id="deployment_id",
limit=1,
offset=0,
+ query="query",
since="2025-06-20T12:00:00Z",
status="queued",
version="version",
@@ -524,6 +525,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> N
deployment_id="deployment_id",
limit=1,
offset=0,
+ query="query",
since="2025-06-20T12:00:00Z",
status="queued",
version="version",
diff --git a/tests/api_resources/test_profiles.py b/tests/api_resources/test_profiles.py
index 296593ea..9317cf48 100644
--- a/tests/api_resources/test_profiles.py
+++ b/tests/api_resources/test_profiles.py
@@ -11,7 +11,9 @@
from kernel import Kernel, AsyncKernel
from tests.utils import assert_matches_type
-from kernel.types import Profile
+from kernel.types import (
+ Profile,
+)
from kernel._response import (
BinaryAPIResponse,
AsyncBinaryAPIResponse,
@@ -104,6 +106,52 @@ def test_path_params_retrieve(self, client: Kernel) -> None:
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ profile = client.profiles.update(
+ id_or_name="id_or_name",
+ name="my-renamed-profile",
+ )
+ assert_matches_type(Profile, profile, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.profiles.with_raw_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-profile",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ profile = response.parse()
+ assert_matches_type(Profile, profile, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.profiles.with_streaming_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-profile",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ profile = response.parse()
+ assert_matches_type(Profile, profile, 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_or_name` but received ''"):
+ client.profiles.with_raw_response.update(
+ id_or_name="",
+ name="my-renamed-profile",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_list(self, client: Kernel) -> None:
@@ -115,6 +163,7 @@ def test_method_list(self, client: Kernel) -> None:
def test_method_list_with_all_params(self, client: Kernel) -> None:
profile = client.profiles.list(
limit=1,
+ name="name",
offset=0,
query="query",
)
@@ -189,7 +238,20 @@ def test_path_params_delete(self, client: Kernel) -> None:
def test_method_download(self, client: Kernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = client.profiles.download(
- "id_or_name",
+ id_or_name="id_or_name",
+ )
+ assert profile.is_closed
+ assert profile.json() == {"foo": "bar"}
+ assert cast(Any, profile.is_closed) is True
+ assert isinstance(profile, BinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_method_download_with_all_params(self, client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ profile = client.profiles.download(
+ id_or_name="id_or_name",
+ format="tar.zst",
)
assert profile.is_closed
assert profile.json() == {"foo": "bar"}
@@ -202,7 +264,7 @@ def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) ->
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = client.profiles.with_raw_response.download(
- "id_or_name",
+ id_or_name="id_or_name",
)
assert profile.is_closed is True
@@ -215,7 +277,7 @@ def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) ->
def test_streaming_response_download(self, client: Kernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
with client.profiles.with_streaming_response.download(
- "id_or_name",
+ id_or_name="id_or_name",
) as profile:
assert not profile.is_closed
assert profile.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -231,7 +293,7 @@ def test_streaming_response_download(self, client: Kernel, respx_mock: MockRoute
def test_path_params_download(self, client: Kernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.profiles.with_raw_response.download(
- "",
+ id_or_name="",
)
@@ -318,6 +380,52 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ profile = await async_client.profiles.update(
+ id_or_name="id_or_name",
+ name="my-renamed-profile",
+ )
+ assert_matches_type(Profile, profile, 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.profiles.with_raw_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-profile",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ profile = await response.parse()
+ assert_matches_type(Profile, profile, 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.profiles.with_streaming_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-profile",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ profile = await response.parse()
+ assert_matches_type(Profile, profile, 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_or_name` but received ''"):
+ await async_client.profiles.with_raw_response.update(
+ id_or_name="",
+ name="my-renamed-profile",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
@@ -329,6 +437,7 @@ async def test_method_list(self, async_client: AsyncKernel) -> None:
async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
profile = await async_client.profiles.list(
limit=1,
+ name="name",
offset=0,
query="query",
)
@@ -403,7 +512,20 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None:
async def test_method_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = await async_client.profiles.download(
- "id_or_name",
+ id_or_name="id_or_name",
+ )
+ assert profile.is_closed
+ assert await profile.json() == {"foo": "bar"}
+ assert cast(Any, profile.is_closed) is True
+ assert isinstance(profile, AsyncBinaryAPIResponse)
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_method_download_with_all_params(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ profile = await async_client.profiles.download(
+ id_or_name="id_or_name",
+ format="tar.zst",
)
assert profile.is_closed
assert await profile.json() == {"foo": "bar"}
@@ -416,7 +538,7 @@ async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = await async_client.profiles.with_raw_response.download(
- "id_or_name",
+ id_or_name="id_or_name",
)
assert profile.is_closed is True
@@ -429,7 +551,7 @@ async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock
async def test_streaming_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
async with async_client.profiles.with_streaming_response.download(
- "id_or_name",
+ id_or_name="id_or_name",
) as profile:
assert not profile.is_closed
assert profile.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -445,5 +567,5 @@ async def test_streaming_response_download(self, async_client: AsyncKernel, resp
async def test_path_params_download(self, async_client: AsyncKernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.profiles.with_raw_response.download(
- "",
+ id_or_name="",
)
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index bb301454..6eaf3b62 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -56,7 +56,7 @@ def test_streaming_response_create(self, client: Kernel) -> None:
@parametrize
def test_method_retrieve(self, client: Kernel) -> None:
project = client.projects.retrieve(
- "id",
+ "id_or_name",
)
assert_matches_type(Project, project, path=["response"])
@@ -64,7 +64,7 @@ def test_method_retrieve(self, client: Kernel) -> None:
@parametrize
def test_raw_response_retrieve(self, client: Kernel) -> None:
response = client.projects.with_raw_response.retrieve(
- "id",
+ "id_or_name",
)
assert response.is_closed is True
@@ -76,7 +76,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_retrieve(self, client: Kernel) -> None:
with client.projects.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"
@@ -89,7 +89,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.projects.with_raw_response.retrieve(
"",
)
@@ -98,7 +98,7 @@ def test_path_params_retrieve(self, client: Kernel) -> None:
@parametrize
def test_method_update(self, client: Kernel) -> None:
project = client.projects.update(
- id="id",
+ id_or_name="id_or_name",
)
assert_matches_type(Project, project, path=["response"])
@@ -106,8 +106,8 @@ def test_method_update(self, client: Kernel) -> None:
@parametrize
def test_method_update_with_all_params(self, client: Kernel) -> None:
project = client.projects.update(
- id="id",
- name="name",
+ id_or_name="id_or_name",
+ name="x",
status="active",
)
assert_matches_type(Project, project, path=["response"])
@@ -116,7 +116,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_update(self, client: Kernel) -> None:
response = client.projects.with_raw_response.update(
- id="id",
+ id_or_name="id_or_name",
)
assert response.is_closed is True
@@ -128,7 +128,7 @@ def test_raw_response_update(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_update(self, client: Kernel) -> None:
with client.projects.with_streaming_response.update(
- 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"
@@ -141,9 +141,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.projects.with_raw_response.update(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -157,6 +157,7 @@ def test_method_list(self, client: Kernel) -> None:
def test_method_list_with_all_params(self, client: Kernel) -> None:
project = client.projects.list(
limit=100,
+ name="name",
offset=0,
query="query",
)
@@ -188,7 +189,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
@parametrize
def test_method_delete(self, client: Kernel) -> None:
project = client.projects.delete(
- "id",
+ "id_or_name",
)
assert project is None
@@ -196,7 +197,7 @@ def test_method_delete(self, client: Kernel) -> None:
@parametrize
def test_raw_response_delete(self, client: Kernel) -> None:
response = client.projects.with_raw_response.delete(
- "id",
+ "id_or_name",
)
assert response.is_closed is True
@@ -208,7 +209,7 @@ def test_raw_response_delete(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_delete(self, client: Kernel) -> None:
with client.projects.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"
@@ -221,7 +222,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.projects.with_raw_response.delete(
"",
)
@@ -270,7 +271,7 @@ async def test_streaming_response_create(self, async_client: AsyncKernel) -> Non
@parametrize
async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
project = await async_client.projects.retrieve(
- "id",
+ "id_or_name",
)
assert_matches_type(Project, project, path=["response"])
@@ -278,7 +279,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.projects.with_raw_response.retrieve(
- "id",
+ "id_or_name",
)
assert response.is_closed is True
@@ -290,7 +291,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.projects.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"
@@ -303,7 +304,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.projects.with_raw_response.retrieve(
"",
)
@@ -312,7 +313,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_update(self, async_client: AsyncKernel) -> None:
project = await async_client.projects.update(
- id="id",
+ id_or_name="id_or_name",
)
assert_matches_type(Project, project, path=["response"])
@@ -320,8 +321,8 @@ async def test_method_update(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
project = await async_client.projects.update(
- id="id",
- name="name",
+ id_or_name="id_or_name",
+ name="x",
status="active",
)
assert_matches_type(Project, project, path=["response"])
@@ -330,7 +331,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
response = await async_client.projects.with_raw_response.update(
- id="id",
+ id_or_name="id_or_name",
)
assert response.is_closed is True
@@ -342,7 +343,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.projects.with_streaming_response.update(
- 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"
@@ -355,9 +356,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.projects.with_raw_response.update(
- id="",
+ id_or_name="",
)
@pytest.mark.skip(reason="Mock server tests are disabled")
@@ -371,6 +372,7 @@ async def test_method_list(self, async_client: AsyncKernel) -> None:
async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
project = await async_client.projects.list(
limit=100,
+ name="name",
offset=0,
query="query",
)
@@ -402,7 +404,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_delete(self, async_client: AsyncKernel) -> None:
project = await async_client.projects.delete(
- "id",
+ "id_or_name",
)
assert project is None
@@ -410,7 +412,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.projects.with_raw_response.delete(
- "id",
+ "id_or_name",
)
assert response.is_closed is True
@@ -422,7 +424,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.projects.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"
@@ -435,7 +437,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.projects.with_raw_response.delete(
"",
)
diff --git a/tests/api_resources/test_proxies.py b/tests/api_resources/test_proxies.py
index fd8080ee..a8737804 100644
--- a/tests/api_resources/test_proxies.py
+++ b/tests/api_resources/test_proxies.py
@@ -13,8 +13,10 @@
ProxyListResponse,
ProxyCheckResponse,
ProxyCreateResponse,
+ ProxyUpdateResponse,
ProxyRetrieveResponse,
)
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -26,7 +28,7 @@ class TestProxies:
@parametrize
def test_method_create(self, client: Kernel) -> None:
proxy = client.proxies.create(
- type="datacenter",
+ type="isp",
)
assert_matches_type(ProxyCreateResponse, proxy, path=["response"])
@@ -34,7 +36,7 @@ def test_method_create(self, client: Kernel) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Kernel) -> None:
proxy = client.proxies.create(
- type="datacenter",
+ type="isp",
bypass_hosts=["string"],
config={"country": "US"},
name="name",
@@ -46,7 +48,7 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
@parametrize
def test_raw_response_create(self, client: Kernel) -> None:
response = client.proxies.with_raw_response.create(
- type="datacenter",
+ type="isp",
)
assert response.is_closed is True
@@ -58,7 +60,7 @@ def test_raw_response_create(self, client: Kernel) -> None:
@parametrize
def test_streaming_response_create(self, client: Kernel) -> None:
with client.proxies.with_streaming_response.create(
- type="datacenter",
+ type="isp",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -72,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"])
@@ -80,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
@@ -92,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"
@@ -105,16 +107,73 @@ 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(
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ proxy = client.proxies.update(
+ id_or_name="id_or_name",
+ name="my-renamed-proxy",
+ )
+ assert_matches_type(ProxyUpdateResponse, proxy, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.proxies.with_raw_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-proxy",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ proxy = response.parse()
+ assert_matches_type(ProxyUpdateResponse, proxy, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.proxies.with_streaming_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-proxy",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ proxy = response.parse()
+ assert_matches_type(ProxyUpdateResponse, proxy, 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_or_name` but received ''"):
+ client.proxies.with_raw_response.update(
+ id_or_name="",
+ name="my-renamed-proxy",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_list(self, client: Kernel) -> None:
proxy = client.proxies.list()
- assert_matches_type(ProxyListResponse, proxy, path=["response"])
+ assert_matches_type(SyncOffsetPagination[ProxyListResponse], proxy, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ proxy = client.proxies.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(SyncOffsetPagination[ProxyListResponse], proxy, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -124,7 +183,7 @@ def test_raw_response_list(self, client: Kernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
proxy = response.parse()
- assert_matches_type(ProxyListResponse, proxy, path=["response"])
+ assert_matches_type(SyncOffsetPagination[ProxyListResponse], proxy, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -134,7 +193,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
proxy = response.parse()
- assert_matches_type(ProxyListResponse, proxy, path=["response"])
+ assert_matches_type(SyncOffsetPagination[ProxyListResponse], proxy, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -142,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
@@ -150,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
@@ -162,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"
@@ -175,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(
"",
)
@@ -184,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"])
@@ -192,8 +251,8 @@ 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",
- url="url",
+ id_or_name="id_or_name",
+ url="https://example.com",
)
assert_matches_type(ProxyCheckResponse, proxy, path=["response"])
@@ -201,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
@@ -213,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"
@@ -226,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="",
)
@@ -241,7 +300,7 @@ class TestAsyncProxies:
@parametrize
async def test_method_create(self, async_client: AsyncKernel) -> None:
proxy = await async_client.proxies.create(
- type="datacenter",
+ type="isp",
)
assert_matches_type(ProxyCreateResponse, proxy, path=["response"])
@@ -249,7 +308,7 @@ async def test_method_create(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None:
proxy = await async_client.proxies.create(
- type="datacenter",
+ type="isp",
bypass_hosts=["string"],
config={"country": "US"},
name="name",
@@ -261,7 +320,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
@parametrize
async def test_raw_response_create(self, async_client: AsyncKernel) -> None:
response = await async_client.proxies.with_raw_response.create(
- type="datacenter",
+ type="isp",
)
assert response.is_closed is True
@@ -273,7 +332,7 @@ async def test_raw_response_create(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncKernel) -> None:
async with async_client.proxies.with_streaming_response.create(
- type="datacenter",
+ type="isp",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -287,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"])
@@ -295,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
@@ -307,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"
@@ -320,16 +379,73 @@ 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(
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ proxy = await async_client.proxies.update(
+ id_or_name="id_or_name",
+ name="my-renamed-proxy",
+ )
+ assert_matches_type(ProxyUpdateResponse, proxy, 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.proxies.with_raw_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-proxy",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ proxy = await response.parse()
+ assert_matches_type(ProxyUpdateResponse, proxy, 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.proxies.with_streaming_response.update(
+ id_or_name="id_or_name",
+ name="my-renamed-proxy",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ proxy = await response.parse()
+ assert_matches_type(ProxyUpdateResponse, proxy, 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_or_name` but received ''"):
+ await async_client.proxies.with_raw_response.update(
+ id_or_name="",
+ name="my-renamed-proxy",
+ )
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
proxy = await async_client.proxies.list()
- assert_matches_type(ProxyListResponse, proxy, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[ProxyListResponse], proxy, 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:
+ proxy = await async_client.proxies.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(AsyncOffsetPagination[ProxyListResponse], proxy, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -339,7 +455,7 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
proxy = await response.parse()
- assert_matches_type(ProxyListResponse, proxy, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[ProxyListResponse], proxy, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -349,7 +465,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
proxy = await response.parse()
- assert_matches_type(ProxyListResponse, proxy, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[ProxyListResponse], proxy, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -357,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
@@ -365,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
@@ -377,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"
@@ -390,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(
"",
)
@@ -399,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"])
@@ -407,8 +523,8 @@ 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",
- url="url",
+ id_or_name="id_or_name",
+ url="https://example.com",
)
assert_matches_type(ProxyCheckResponse, proxy, path=["response"])
@@ -416,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
@@ -428,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"
@@ -441,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="",
)
diff --git a/tests/api_resources/test_search.py b/tests/api_resources/test_search.py
new file mode 100644
index 00000000..ef559856
--- /dev/null
+++ b/tests/api_resources/test_search.py
@@ -0,0 +1,265 @@
+# 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._utils import parse_date
+from kernel.types.search import Search
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSearch:
+ 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:
+ search = client.search.create(
+ query="x",
+ )
+ assert_matches_type(Search, search, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Kernel) -> None:
+ search = client.search.create(
+ query="x",
+ content=True,
+ country="se",
+ end_date=parse_date("2019-12-27"),
+ exclude_domains=["string"],
+ include_domains=["string"],
+ include_raw=True,
+ language="language",
+ max_results=1,
+ recency="hour",
+ safe_search="off",
+ start_date=parse_date("2019-12-27"),
+ strategy={
+ "type": "auto",
+ "fallback_on": ["error"],
+ "provider_options": [
+ {
+ "provider": "brave",
+ "options": {
+ "count": 1,
+ "extra_snippets": True,
+ "goggles": "goggles",
+ "goggles_id": "goggles_id",
+ "include_fetch_metadata": True,
+ "offset": 0,
+ "operators": "operators",
+ "result_filter": "result_filter",
+ "search_lang": "search_lang",
+ "spellcheck": True,
+ "ui_lang": "ui_lang",
+ "units": "metric",
+ },
+ }
+ ],
+ },
+ strict_params=True,
+ timeout_ms=1000,
+ )
+ assert_matches_type(Search, search, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Kernel) -> None:
+ response = client.search.with_raw_response.create(
+ query="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ search = response.parse()
+ assert_matches_type(Search, search, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Kernel) -> None:
+ with client.search.with_streaming_response.create(
+ query="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ search = response.parse()
+ assert_matches_type(Search, search, 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:
+ search = client.search.retrieve(
+ "srch_abc123",
+ )
+ assert_matches_type(Search, search, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.search.with_raw_response.retrieve(
+ "srch_abc123",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ search = response.parse()
+ assert_matches_type(Search, search, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.search.with_streaming_response.retrieve(
+ "srch_abc123",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ search = response.parse()
+ assert_matches_type(Search, search, 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.search.with_raw_response.retrieve(
+ "",
+ )
+
+
+class TestAsyncSearch:
+ 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:
+ search = await async_client.search.create(
+ query="x",
+ )
+ assert_matches_type(Search, search, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None:
+ search = await async_client.search.create(
+ query="x",
+ content=True,
+ country="se",
+ end_date=parse_date("2019-12-27"),
+ exclude_domains=["string"],
+ include_domains=["string"],
+ include_raw=True,
+ language="language",
+ max_results=1,
+ recency="hour",
+ safe_search="off",
+ start_date=parse_date("2019-12-27"),
+ strategy={
+ "type": "auto",
+ "fallback_on": ["error"],
+ "provider_options": [
+ {
+ "provider": "brave",
+ "options": {
+ "count": 1,
+ "extra_snippets": True,
+ "goggles": "goggles",
+ "goggles_id": "goggles_id",
+ "include_fetch_metadata": True,
+ "offset": 0,
+ "operators": "operators",
+ "result_filter": "result_filter",
+ "search_lang": "search_lang",
+ "spellcheck": True,
+ "ui_lang": "ui_lang",
+ "units": "metric",
+ },
+ }
+ ],
+ },
+ strict_params=True,
+ timeout_ms=1000,
+ )
+ assert_matches_type(Search, search, 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.search.with_raw_response.create(
+ query="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ search = await response.parse()
+ assert_matches_type(Search, search, 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.search.with_streaming_response.create(
+ query="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ search = await response.parse()
+ assert_matches_type(Search, search, 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:
+ search = await async_client.search.retrieve(
+ "srch_abc123",
+ )
+ assert_matches_type(Search, search, 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.search.with_raw_response.retrieve(
+ "srch_abc123",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ search = await response.parse()
+ assert_matches_type(Search, search, 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.search.with_streaming_response.retrieve(
+ "srch_abc123",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ search = await response.parse()
+ assert_matches_type(Search, search, 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.search.with_raw_response.retrieve(
+ "",
+ )
diff --git a/tests/api_resources/test_vault_provider_configs.py b/tests/api_resources/test_vault_provider_configs.py
new file mode 100644
index 00000000..7fe512e3
--- /dev/null
+++ b/tests/api_resources/test_vault_provider_configs.py
@@ -0,0 +1,603 @@
+# 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 (
+ VaultProviderConfig,
+)
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVaultProviderConfigs:
+ 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_overload_1(self, client: Kernel) -> None:
+ vault_provider_config = client.vault_provider_configs.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="link",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params_overload_1(self, client: Kernel) -> None:
+ vault_provider_config = client.vault_provider_configs.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ "publishable_key": "pk_test_lK9w2kI5J1",
+ },
+ name="name",
+ provider="link",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create_overload_1(self, client: Kernel) -> None:
+ response = client.vault_provider_configs.with_raw_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="link",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create_overload_1(self, client: Kernel) -> None:
+ with client.vault_provider_configs.with_streaming_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="link",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_overload_2(self, client: Kernel) -> None:
+ vault_provider_config = client.vault_provider_configs.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="agentcard",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create_overload_2(self, client: Kernel) -> None:
+ response = client.vault_provider_configs.with_raw_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="agentcard",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create_overload_2(self, client: Kernel) -> None:
+ with client.vault_provider_configs.with_streaming_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="agentcard",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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:
+ vault_provider_config = client.vault_provider_configs.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.vault_provider_configs.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.vault_provider_configs.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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_or_name` but received ''"):
+ client.vault_provider_configs.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ vault_provider_config = client.vault_provider_configs.update(
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Kernel) -> None:
+ vault_provider_config = client.vault_provider_configs.update(
+ id_or_name="id_or_name",
+ credentials={
+ "client_secret": "x",
+ "publishable_key": "pk_test_lK9w2kI5J1",
+ },
+ name="renamed-link-client",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.vault_provider_configs.with_raw_response.update(
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.vault_provider_configs.with_streaming_response.update(
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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_or_name` but received ''"):
+ client.vault_provider_configs.with_raw_response.update(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ vault_provider_config = client.vault_provider_configs.list()
+ assert_matches_type(SyncOffsetPagination[VaultProviderConfig], vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ vault_provider_config = client.vault_provider_configs.list(
+ limit=1,
+ offset=0,
+ )
+ assert_matches_type(SyncOffsetPagination[VaultProviderConfig], vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.vault_provider_configs.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = response.parse()
+ assert_matches_type(SyncOffsetPagination[VaultProviderConfig], vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.vault_provider_configs.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = response.parse()
+ assert_matches_type(SyncOffsetPagination[VaultProviderConfig], vault_provider_config, 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:
+ vault_provider_config = client.vault_provider_configs.delete(
+ "id_or_name",
+ )
+ assert vault_provider_config is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.vault_provider_configs.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = response.parse()
+ assert vault_provider_config is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.vault_provider_configs.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = response.parse()
+ assert vault_provider_config 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_or_name` but received ''"):
+ client.vault_provider_configs.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncVaultProviderConfigs:
+ 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_overload_1(self, async_client: AsyncKernel) -> None:
+ vault_provider_config = await async_client.vault_provider_configs.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="link",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params_overload_1(self, async_client: AsyncKernel) -> None:
+ vault_provider_config = await async_client.vault_provider_configs.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ "publishable_key": "pk_test_lK9w2kI5J1",
+ },
+ name="name",
+ provider="link",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create_overload_1(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vault_provider_configs.with_raw_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="link",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create_overload_1(self, async_client: AsyncKernel) -> None:
+ async with async_client.vault_provider_configs.with_streaming_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="link",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_overload_2(self, async_client: AsyncKernel) -> None:
+ vault_provider_config = await async_client.vault_provider_configs.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="agentcard",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create_overload_2(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vault_provider_configs.with_raw_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="agentcard",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create_overload_2(self, async_client: AsyncKernel) -> None:
+ async with async_client.vault_provider_configs.with_streaming_response.create(
+ credentials={
+ "client_id": "x",
+ "client_secret": "x",
+ },
+ name="name",
+ provider="agentcard",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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:
+ vault_provider_config = await async_client.vault_provider_configs.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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.vault_provider_configs.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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.vault_provider_configs.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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_or_name` but received ''"):
+ await async_client.vault_provider_configs.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ vault_provider_config = await async_client.vault_provider_configs.update(
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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:
+ vault_provider_config = await async_client.vault_provider_configs.update(
+ id_or_name="id_or_name",
+ credentials={
+ "client_secret": "x",
+ "publishable_key": "pk_test_lK9w2kI5J1",
+ },
+ name="renamed-link-client",
+ )
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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.vault_provider_configs.with_raw_response.update(
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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.vault_provider_configs.with_streaming_response.update(
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = await response.parse()
+ assert_matches_type(VaultProviderConfig, vault_provider_config, 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_or_name` but received ''"):
+ await async_client.vault_provider_configs.with_raw_response.update(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ vault_provider_config = await async_client.vault_provider_configs.list()
+ assert_matches_type(AsyncOffsetPagination[VaultProviderConfig], vault_provider_config, 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:
+ vault_provider_config = await async_client.vault_provider_configs.list(
+ limit=1,
+ offset=0,
+ )
+ assert_matches_type(AsyncOffsetPagination[VaultProviderConfig], vault_provider_config, 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.vault_provider_configs.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[VaultProviderConfig], vault_provider_config, 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.vault_provider_configs.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[VaultProviderConfig], vault_provider_config, 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:
+ vault_provider_config = await async_client.vault_provider_configs.delete(
+ "id_or_name",
+ )
+ assert vault_provider_config 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.vault_provider_configs.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault_provider_config = await response.parse()
+ assert vault_provider_config 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.vault_provider_configs.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault_provider_config = await response.parse()
+ assert vault_provider_config 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_or_name` but received ''"):
+ await async_client.vault_provider_configs.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/test_vaults.py b/tests/api_resources/test_vaults.py
new file mode 100644
index 00000000..7372f855
--- /dev/null
+++ b/tests/api_resources/test_vaults.py
@@ -0,0 +1,421 @@
+# 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 Vault, VaultEncryptionKey
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVaults:
+ 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:
+ vault = client.vaults.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(Vault, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.vaults.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = response.parse()
+ assert_matches_type(Vault, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.vaults.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = response.parse()
+ assert_matches_type(Vault, vault, 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_or_name` but received ''"):
+ client.vaults.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ vault = client.vaults.list()
+ assert_matches_type(SyncOffsetPagination[Vault], vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ vault = client.vaults.list(
+ limit=1,
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(SyncOffsetPagination[Vault], vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.vaults.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = response.parse()
+ assert_matches_type(SyncOffsetPagination[Vault], vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.vaults.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = response.parse()
+ assert_matches_type(SyncOffsetPagination[Vault], vault, 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:
+ vault = client.vaults.delete(
+ "id_or_name",
+ )
+ assert vault is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.vaults.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = response.parse()
+ assert vault is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.vaults.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = response.parse()
+ assert vault 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_or_name` but received ''"):
+ client.vaults.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve_encryption_key(self, client: Kernel) -> None:
+ vault = client.vaults.retrieve_encryption_key(
+ "id_or_name",
+ )
+ assert_matches_type(VaultEncryptionKey, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve_encryption_key(self, client: Kernel) -> None:
+ response = client.vaults.with_raw_response.retrieve_encryption_key(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = response.parse()
+ assert_matches_type(VaultEncryptionKey, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve_encryption_key(self, client: Kernel) -> None:
+ with client.vaults.with_streaming_response.retrieve_encryption_key(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = response.parse()
+ assert_matches_type(VaultEncryptionKey, vault, 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_encryption_key(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.with_raw_response.retrieve_encryption_key(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert(self, client: Kernel) -> None:
+ vault = client.vaults.upsert(
+ name="checkout",
+ )
+ assert_matches_type(Vault, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_upsert(self, client: Kernel) -> None:
+ response = client.vaults.with_raw_response.upsert(
+ name="checkout",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = response.parse()
+ assert_matches_type(Vault, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_upsert(self, client: Kernel) -> None:
+ with client.vaults.with_streaming_response.upsert(
+ name="checkout",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = response.parse()
+ assert_matches_type(Vault, vault, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncVaults:
+ 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:
+ vault = await async_client.vaults.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(Vault, vault, 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.vaults.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = await response.parse()
+ assert_matches_type(Vault, vault, 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.vaults.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = await response.parse()
+ assert_matches_type(Vault, vault, 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_or_name` but received ''"):
+ await async_client.vaults.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ vault = await async_client.vaults.list()
+ assert_matches_type(AsyncOffsetPagination[Vault], vault, 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:
+ vault = await async_client.vaults.list(
+ limit=1,
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(AsyncOffsetPagination[Vault], vault, 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.vaults.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[Vault], vault, 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.vaults.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[Vault], vault, 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:
+ vault = await async_client.vaults.delete(
+ "id_or_name",
+ )
+ assert vault 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.vaults.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = await response.parse()
+ assert vault 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.vaults.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = await response.parse()
+ assert vault 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_or_name` but received ''"):
+ await async_client.vaults.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve_encryption_key(self, async_client: AsyncKernel) -> None:
+ vault = await async_client.vaults.retrieve_encryption_key(
+ "id_or_name",
+ )
+ assert_matches_type(VaultEncryptionKey, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve_encryption_key(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.with_raw_response.retrieve_encryption_key(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = await response.parse()
+ assert_matches_type(VaultEncryptionKey, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve_encryption_key(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.with_streaming_response.retrieve_encryption_key(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = await response.parse()
+ assert_matches_type(VaultEncryptionKey, vault, 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_encryption_key(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.with_raw_response.retrieve_encryption_key(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert(self, async_client: AsyncKernel) -> None:
+ vault = await async_client.vaults.upsert(
+ name="checkout",
+ )
+ assert_matches_type(Vault, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_upsert(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.with_raw_response.upsert(
+ name="checkout",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vault = await response.parse()
+ assert_matches_type(Vault, vault, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_upsert(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.with_streaming_response.upsert(
+ name="checkout",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vault = await response.parse()
+ assert_matches_type(Vault, vault, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/vaults/__init__.py b/tests/api_resources/vaults/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/vaults/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/vaults/test_items.py b/tests/api_resources/vaults/test_items.py
new file mode 100644
index 00000000..96363302
--- /dev/null
+++ b/tests/api_resources/vaults/test_items.py
@@ -0,0 +1,3892 @@
+# 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._utils import parse_datetime
+from kernel.types.vaults import (
+ VaultItem,
+ ItemListResponse,
+ ItemEventsResponse,
+ VaultItemOperationResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestItems:
+ 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:
+ item = client.vaults.items.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve_with_all_params(self, client: Kernel) -> None:
+ item = client.vaults.items.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ expand=["payment_methods"],
+ wait=0,
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItem, item, 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_or_name` but received ''"):
+ client.vaults.items.with_raw_response.retrieve(
+ key="x",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.retrieve(
+ key="",
+ id_or_name="id_or_name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_overload_1(self, client: Kernel) -> None:
+ item = client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params_overload_1(self, client: Kernel) -> None:
+ item = client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ "line_items": [
+ {
+ "name": "name",
+ "description": "description",
+ "image_url": "image_url",
+ "product_url": "product_url",
+ "quantity": 1,
+ "sku": "sku",
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ "unit_amount": 0,
+ "url": "url",
+ }
+ ],
+ "metadata": {"foo": "string"},
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ },
+ type="card",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update_overload_1(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update_overload_1(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItem, item, 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_overload_1(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.update(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_overload_2(self, client: Kernel) -> None:
+ item = client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params_overload_2(self, client: Kernel) -> None:
+ item = client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "add_fields": [
+ {
+ "name": "name",
+ "type": "text",
+ "label": "label",
+ "required": True,
+ "sensitive": True,
+ }
+ ],
+ "description": "description",
+ "fields": {
+ "foo": {
+ "encrypted_value": "x",
+ "value": "value",
+ }
+ },
+ "remove_fields": ["x"],
+ },
+ type="credential",
+ version=1,
+ expected_item_id="x",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update_overload_2(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update_overload_2(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItem, item, 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_overload_2(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="",
+ spec={},
+ type="credential",
+ version=1,
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.update(
+ key="",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ item = client.vaults.items.list(
+ "id_or_name",
+ )
+ assert_matches_type(ItemListResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.list(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(ItemListResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.list(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(ItemListResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Kernel) -> None:
+ item = client.vaults.items.delete(
+ key="x",
+ id_or_name="id_or_name",
+ )
+ assert item is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.delete(
+ key="x",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert item is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.delete(
+ key="x",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert item 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_or_name` but received ''"):
+ client.vaults.items.with_raw_response.delete(
+ key="x",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.delete(
+ key="",
+ id_or_name="id_or_name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_events(self, client: Kernel) -> None:
+ item = client.vaults.items.events(
+ key="key",
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(ItemEventsResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_events_with_all_params(self, client: Kernel) -> None:
+ item = client.vaults.items.events(
+ key="key",
+ id_or_name="id_or_name",
+ after="after",
+ wait=0,
+ )
+ assert_matches_type(ItemEventsResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_events(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.events(
+ key="key",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(ItemEventsResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_events(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.events(
+ key="key",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(ItemEventsResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_events(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.events(
+ key="key",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.events(
+ key="",
+ id_or_name="id_or_name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_1(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_1(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ browser_id="x",
+ page_url="https://example.com",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_1(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_1(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_1(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="authorize",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="authorize",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_2(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_2(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_2(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_2(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="restart_verification",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_3(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_3(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_3(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_3(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_4(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="collect",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_4(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="collect",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_4(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="collect",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_4(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="collect",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="collect",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_5(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_5(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ "psp": "square",
+ },
+ type="prepare_checkout",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_5(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_5(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_5(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_6(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_6(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ fields=[
+ {
+ "field": "number",
+ "selector": "#card-number",
+ "format": "MM/YY",
+ },
+ {
+ "field": "exp_month",
+ "selector": "#expiry-month",
+ "format": "MM/YY",
+ },
+ {
+ "field": "exp_year",
+ "selector": "#expiry-year",
+ "format": "MM/YY",
+ },
+ {
+ "field": "cvc",
+ "selector": "#security-code",
+ "format": "MM/YY",
+ },
+ ],
+ page_url="https://shop.example/checkout",
+ timeout_ms=1,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_6(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_6(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_6(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_7(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_7(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ goal="goal",
+ keywords=["x"],
+ reason="reason",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_7(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_7(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_7(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="1pw_create_access_request",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_8(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_8(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ timeout_seconds=0,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_8(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_8(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_8(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="1pw_access_request_status",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_9(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_9(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ entry_id="x",
+ timeout_ms=1,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_9(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_9(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_9(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_10(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_10(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_10(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_10(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="1pw_recover",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_11(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_11(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ access_token_expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_11(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_11(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_11(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_overload_12(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_perform_operation_with_all_params_overload_12(self, client: Kernel) -> None:
+ item = client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ "format": "format",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ "format": "format",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ timeout_sec=1,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_perform_operation_overload_12(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_perform_operation_overload_12(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_perform_operation_overload_12(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert_overload_1(self, client: Kernel) -> None:
+ item = client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert_with_all_params_overload_1(self, client: Kernel) -> None:
+ item = client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_upsert_overload_1(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_upsert_overload_1(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_upsert_overload_1(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert_overload_2(self, client: Kernel) -> None:
+ item = client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert_with_all_params_overload_2(self, client: Kernel) -> None:
+ item = client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ "line_items": [
+ {
+ "name": "name",
+ "description": "description",
+ "image_url": "image_url",
+ "product_url": "product_url",
+ "quantity": 1,
+ "sku": "sku",
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ "unit_amount": 0,
+ "url": "url",
+ }
+ ],
+ "metadata": {"foo": "string"},
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ },
+ type="card",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_upsert_overload_2(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_upsert_overload_2(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_upsert_overload_2(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert_overload_3(self, client: Kernel) -> None:
+ item = client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_upsert_overload_3(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_upsert_overload_3(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_upsert_overload_3(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert_overload_4(self, client: Kernel) -> None:
+ item = client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_upsert_with_all_params_overload_4(self, client: Kernel) -> None:
+ item = client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ "encrypted_value": "x",
+ "label": "label",
+ "required": True,
+ "sensitive": True,
+ "value": "x",
+ }
+ ],
+ "provider": "kernel",
+ "description": "description",
+ },
+ type="credential",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_upsert_overload_4(self, client: Kernel) -> None:
+ response = client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_upsert_overload_4(self, client: Kernel) -> None:
+ with client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_upsert_overload_4(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
+
+
+class TestAsyncItems:
+ 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:
+ item = await async_client.vaults.items.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve_with_all_params(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ expand=["payment_methods"],
+ wait=0,
+ )
+ assert_matches_type(VaultItem, item, 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.vaults.items.with_raw_response.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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.vaults.items.with_streaming_response.retrieve(
+ key="x",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.retrieve(
+ key="x",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.retrieve(
+ key="",
+ id_or_name="id_or_name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_overload_1(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params_overload_1(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ "line_items": [
+ {
+ "name": "name",
+ "description": "description",
+ "image_url": "image_url",
+ "product_url": "product_url",
+ "quantity": 1,
+ "sku": "sku",
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ "unit_amount": 0,
+ "url": "url",
+ }
+ ],
+ "metadata": {"foo": "string"},
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ },
+ type="card",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update_overload_1(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update_overload_1(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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_overload_1(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.update(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_overload_2(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params_overload_2(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "add_fields": [
+ {
+ "name": "name",
+ "type": "text",
+ "label": "label",
+ "required": True,
+ "sensitive": True,
+ }
+ ],
+ "description": "description",
+ "fields": {
+ "foo": {
+ "encrypted_value": "x",
+ "value": "value",
+ }
+ },
+ "remove_fields": ["x"],
+ },
+ type="credential",
+ version=1,
+ expected_item_id="x",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update_overload_2(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update_overload_2(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.update(
+ key="x",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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_overload_2(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.update(
+ key="x",
+ id_or_name="",
+ spec={},
+ type="credential",
+ version=1,
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.update(
+ key="",
+ id_or_name="id_or_name",
+ spec={},
+ type="credential",
+ version=1,
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.list(
+ "id_or_name",
+ )
+ assert_matches_type(ItemListResponse, item, 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.vaults.items.with_raw_response.list(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(ItemListResponse, item, 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.vaults.items.with_streaming_response.list(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(ItemListResponse, item, 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_list(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.delete(
+ key="x",
+ id_or_name="id_or_name",
+ )
+ assert item 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.vaults.items.with_raw_response.delete(
+ key="x",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert item 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.vaults.items.with_streaming_response.delete(
+ key="x",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert item 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_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.delete(
+ key="x",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.delete(
+ key="",
+ id_or_name="id_or_name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_events(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.events(
+ key="key",
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(ItemEventsResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_events_with_all_params(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.events(
+ key="key",
+ id_or_name="id_or_name",
+ after="after",
+ wait=0,
+ )
+ assert_matches_type(ItemEventsResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_events(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.events(
+ key="key",
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(ItemEventsResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_events(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.events(
+ key="key",
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(ItemEventsResponse, item, 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_events(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.events(
+ key="key",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.events(
+ key="",
+ id_or_name="id_or_name",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_1(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_1(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ browser_id="x",
+ page_url="https://example.com",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_1(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_1(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="authorize",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_1(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="authorize",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="authorize",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_2(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_2(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_2(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_2(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="restart_verification",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="restart_verification",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_3(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_3(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_3(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_3(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ amount=0,
+ currency="SEW",
+ occurred_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ status="APPROVED",
+ transaction_type="PURCHASE",
+ type="confirm_transaction",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_4(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="collect",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_4(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="collect",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_4(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="collect",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_4(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="collect",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="collect",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_5(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_5(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ "psp": "square",
+ },
+ type="prepare_checkout",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_5(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_5(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_5(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ checkout={
+ "browser_id": "browser_id",
+ "environment": "production",
+ "merchant_origin": "merchant_origin",
+ },
+ type="prepare_checkout",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_6(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_6(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ fields=[
+ {
+ "field": "number",
+ "selector": "#card-number",
+ "format": "MM/YY",
+ },
+ {
+ "field": "exp_month",
+ "selector": "#expiry-month",
+ "format": "MM/YY",
+ },
+ {
+ "field": "exp_year",
+ "selector": "#expiry-year",
+ "format": "MM/YY",
+ },
+ {
+ "field": "cvc",
+ "selector": "#security-code",
+ "format": "MM/YY",
+ },
+ ],
+ page_url="https://shop.example/checkout",
+ timeout_ms=1,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_6(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_6(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_6(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ browser_id="browser-session-id",
+ type="fill",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_7(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_7(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ goal="goal",
+ keywords=["x"],
+ reason="reason",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_7(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_7(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_7(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="1pw_create_access_request",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="1pw_create_access_request",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_8(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_8(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ timeout_seconds=0,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_8(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_8(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_8(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="1pw_access_request_status",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="1pw_access_request_status",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_9(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_9(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ entry_id="x",
+ timeout_ms=1,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_9(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_9(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_9(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ browser_id="x",
+ page_url="https://example.com",
+ type="1pw_fill",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_10(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_10(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_10(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_10(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ type="1pw_recover",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ type="1pw_recover",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_11(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_11(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ access_token_expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_11(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_11(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_11(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ access_token="x",
+ type="1pw_update_access_token",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_overload_12(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_perform_operation_with_all_params_overload_12(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ "format": "format",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ "format": "format",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ timeout_sec=1,
+ )
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_perform_operation_overload_12(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_perform_operation_overload_12(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.perform_operation(
+ key="key",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItemOperationResponse, item, 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_perform_operation_overload_12(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="key",
+ id_or_name="",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.perform_operation(
+ key="",
+ id_or_name="id_or_name",
+ bindings=[
+ {
+ "field": "email",
+ "input_path": "/email",
+ },
+ {
+ "field": "password",
+ "input_path": "/password",
+ },
+ ],
+ browser_id="browser-session-id",
+ input={
+ "email": "bar",
+ "password": "bar",
+ },
+ page_url="https://accounts.example.com/signin",
+ tool_ref="opaque-live-tool-ref",
+ type="webmcp_invoke",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert_overload_1(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert_with_all_params_overload_1(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_upsert_overload_1(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_upsert_overload_1(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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_upsert_overload_1(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "link",
+ },
+ type="wallet",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert_overload_2(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert_with_all_params_overload_2(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ "line_items": [
+ {
+ "name": "name",
+ "description": "description",
+ "image_url": "image_url",
+ "product_url": "product_url",
+ "quantity": 1,
+ "sku": "sku",
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ "unit_amount": 0,
+ "url": "url",
+ }
+ ],
+ "metadata": {"foo": "string"},
+ "totals": [
+ {
+ "amount": 0,
+ "display_text": "display_text",
+ "type": "type",
+ }
+ ],
+ },
+ type="card",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_upsert_overload_2(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_upsert_overload_2(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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_upsert_overload_2(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "amount": 1,
+ "context": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "currency": "bFx",
+ "merchant_name": "x",
+ "merchant_url": "https://example.com",
+ "payment_method_id": "x",
+ "provider": "link",
+ "wallet": "wallet",
+ },
+ type="card",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert_overload_3(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_upsert_overload_3(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_upsert_overload_3(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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_upsert_overload_3(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "authorization": {
+ "client": {"type": "kernel_managed"},
+ "method": "oauth",
+ },
+ "provider": "1password",
+ },
+ type="credential_account",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert_overload_4(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_upsert_with_all_params_overload_4(self, async_client: AsyncKernel) -> None:
+ item = await async_client.vaults.items.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ "encrypted_value": "x",
+ "label": "label",
+ "required": True,
+ "sensitive": True,
+ "value": "x",
+ }
+ ],
+ "provider": "kernel",
+ "description": "description",
+ },
+ type="credential",
+ )
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_upsert_overload_4(self, async_client: AsyncKernel) -> None:
+ response = await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_upsert_overload_4(self, async_client: AsyncKernel) -> None:
+ async with async_client.vaults.items.with_streaming_response.upsert(
+ key="x",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ item = await response.parse()
+ assert_matches_type(VaultItem, item, 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_upsert_overload_4(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="x",
+ id_or_name="",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.vaults.items.with_raw_response.upsert(
+ key="",
+ id_or_name="id_or_name",
+ spec={
+ "fields": [
+ {
+ "name": "name",
+ "type": "text",
+ }
+ ],
+ "provider": "kernel",
+ },
+ type="credential",
+ )
diff --git a/tests/test_audit_log_download.py b/tests/test_audit_log_download.py
new file mode 100644
index 00000000..e254066e
--- /dev/null
+++ b/tests/test_audit_log_download.py
@@ -0,0 +1,409 @@
+from __future__ import annotations
+
+import hashlib
+import threading
+from io import BytesIO
+from typing import BinaryIO, Iterator, AsyncIterator, cast
+from typing_extensions import override
+
+import httpx
+import pytest
+from respx import MockRouter
+
+from kernel import Kernel, AsyncKernel, InternalServerError, AuditLogDownloadError
+from kernel.lib import audit_log_download
+
+
+def chunk_headers(body: bytes, *, rows: int, has_more: bool, next_cursor: str | None = None) -> dict[str, str]:
+ headers = {
+ "x-content-sha256": hashlib.sha256(body).hexdigest(),
+ "x-has-more": str(has_more).lower(),
+ "x-row-count": str(rows),
+ }
+ if next_cursor is not None:
+ headers["x-next-cursor"] = next_cursor
+ return headers
+
+
+def chunk_response(body: bytes, *, rows: int, has_more: bool, next_cursor: str | None = None) -> httpx.Response:
+ return httpx.Response(
+ 200,
+ content=body,
+ headers=chunk_headers(body, rows=rows, has_more=has_more, next_cursor=next_cursor),
+ )
+
+
+class BrokenSyncByteStream(httpx.SyncByteStream):
+ @override
+ def __iter__(self) -> Iterator[bytes]:
+ yield b"partial"
+ raise httpx.ReadError("truncated response body")
+
+
+class BrokenAsyncByteStream(httpx.AsyncByteStream):
+ @override
+ async def __aiter__(self) -> AsyncIterator[bytes]:
+ yield b"partial"
+ raise httpx.ReadError("truncated response body")
+
+
+def test_download_writes_verified_chunks(client: Kernel, respx_mock: MockRouter) -> None:
+ responses = iter(
+ [
+ chunk_response(b"first", rows=2, has_more=True, next_cursor="next"),
+ chunk_response(b"second", rows=1, has_more=False),
+ ]
+ )
+ cursors: list[str | None] = []
+ formats: list[str | None] = []
+
+ def respond(request: httpx.Request) -> httpx.Response:
+ cursors.append(request.url.params.get("cursor"))
+ formats.append(request.url.params.get("format"))
+ return next(responses)
+
+ respx_mock.get("/audit-logs/export/chunk").mock(side_effect=respond)
+ destination = BytesIO()
+ progress: list[tuple[int, int, int, int]] = []
+
+ result = client.audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ on_progress=lambda update: progress.append(
+ (update.bytes_written, update.chunks, update.rows, update.chunk_rows)
+ ),
+ )
+
+ assert destination.getvalue() == b"firstsecond"
+ assert result.bytes_written == 11
+ assert result.chunks == 2
+ assert result.rows == 3
+ assert progress == [(5, 1, 2, 2), (11, 2, 3, 1)]
+ assert cursors == [None, "next"]
+ assert formats == [None, None]
+
+
+async def test_async_download_writes_verified_chunks(
+ async_client: AsyncKernel, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ responses = iter(
+ [
+ chunk_response(b"first", rows=2, has_more=True, next_cursor="next"),
+ chunk_response(b"second", rows=1, has_more=False),
+ ]
+ )
+ formats: list[str | None] = []
+
+ def respond(request: httpx.Request) -> httpx.Response:
+ formats.append(request.url.params.get("format"))
+ return next(responses)
+
+ respx_mock.get("/audit-logs/export/chunk").mock(side_effect=respond)
+ thread_ids: list[int] = []
+ write_chunk = audit_log_download._write_chunk
+
+ def record_write(destination: BinaryIO, body: bytes) -> None:
+ thread_ids.append(threading.get_ident())
+ write_chunk(destination, body)
+
+ monkeypatch.setattr(audit_log_download, "_write_chunk", record_write)
+ destination = BytesIO()
+ progress_called = False
+
+ async def on_progress(_: object) -> None:
+ nonlocal progress_called
+ progress_called = True
+
+ result = await async_client.audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ on_progress=on_progress,
+ )
+
+ assert destination.getvalue() == b"firstsecond"
+ assert result.bytes_written == 11
+ assert result.chunks == 2
+ assert result.rows == 3
+ assert progress_called
+ assert thread_ids and all(thread_id != threading.get_ident() for thread_id in thread_ids)
+ assert formats == [None, None]
+
+
+async def test_async_download_retries_body_read_failure(
+ async_client: AsyncKernel, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ async def no_sleep(_: float) -> None:
+ pass
+
+ monkeypatch.setattr(audit_log_download.anyio, "sleep", no_sleep)
+ headers = chunk_headers(b"good", rows=1, has_more=False)
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ side_effect=[
+ httpx.Response(200, headers=headers, stream=BrokenAsyncByteStream()),
+ chunk_response(b"good", rows=1, has_more=False),
+ ]
+ )
+ destination = BytesIO()
+
+ await async_client.with_options(max_retries=0).audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ max_transfer_retries=1,
+ )
+
+ assert destination.getvalue() == b"good"
+ assert route.call_count == 2
+
+
+async def test_async_download_retries_checksum_mismatch(
+ async_client: AsyncKernel, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ async def no_sleep(_: float) -> None:
+ pass
+
+ monkeypatch.setattr(audit_log_download.anyio, "sleep", no_sleep)
+ bad = chunk_response(b"bad", rows=1, has_more=False)
+ bad.headers["x-content-sha256"] = hashlib.sha256(b"good").hexdigest()
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ side_effect=[bad, chunk_response(b"good", rows=1, has_more=False)]
+ )
+ destination = BytesIO()
+
+ await async_client.audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert destination.getvalue() == b"good"
+ assert route.call_count == 2
+
+
+async def test_async_download_uses_client_http_retries(async_client: AsyncKernel, respx_mock: MockRouter) -> None:
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ side_effect=[
+ httpx.Response(500, json={"message": "temporary failure"}),
+ chunk_response(b"good", rows=1, has_more=False),
+ ]
+ )
+
+ await async_client.audit_logs.download(
+ to=BytesIO(),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert route.call_count == 2
+
+
+async def test_async_download_respects_disabled_http_retries(async_client: AsyncKernel, respx_mock: MockRouter) -> None:
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ return_value=httpx.Response(500, json={"message": "temporary failure"})
+ )
+
+ with pytest.raises(InternalServerError, match="500"):
+ await async_client.with_options(max_retries=0).audit_logs.download(
+ to=BytesIO(),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert route.call_count == 1
+
+
+@pytest.mark.parametrize("row_count", ["", "1.0", "50001", "9" * 5000])
+def test_download_rejects_invalid_row_count(row_count: str, client: Kernel, respx_mock: MockRouter) -> None:
+ response = chunk_response(b"chunk", rows=1, has_more=False)
+ response.headers["x-row-count"] = row_count
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=response)
+ destination = BytesIO()
+
+ with pytest.raises(AuditLogDownloadError, match="response missing or invalid X-Row-Count header"):
+ client.audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert destination.getvalue() == b""
+
+
+def test_download_accepts_zero_padded_row_count(client: Kernel, respx_mock: MockRouter) -> None:
+ response = chunk_response(b"chunk", rows=1, has_more=False)
+ response.headers["x-row-count"] = "0" * 5000 + "1"
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=response)
+
+ result = client.audit_logs.download(
+ to=BytesIO(),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert result.rows == 1
+
+
+class InvalidWriteDestination:
+ def __init__(self, result: object) -> None:
+ self.result = result
+
+ def write(self, _: object) -> object:
+ return self.result
+
+
+@pytest.mark.parametrize("write_result", [float("nan"), 0.5, True])
+def test_download_rejects_invalid_write_result(write_result: object, client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=chunk_response(b"chunk", rows=1, has_more=False))
+
+ with pytest.raises(AuditLogDownloadError, match="audit log download destination performed a short write"):
+ client.audit_logs.download(
+ to=cast(BinaryIO, InvalidWriteDestination(write_result)),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+
+@pytest.mark.parametrize("max_transfer_retries", [True, 1.5])
+def test_download_rejects_invalid_transfer_retry_count(max_transfer_retries: object, client: Kernel) -> None:
+ with pytest.raises(ValueError, match="max_transfer_retries must be a non-negative integer"):
+ client.audit_logs.download(
+ to=BytesIO(),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ max_transfer_retries=cast(int, max_transfer_retries),
+ )
+
+
+def test_download_retries_body_read_failure(
+ client: Kernel, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ def no_sleep(_: float) -> None:
+ pass
+
+ monkeypatch.setattr(audit_log_download.time, "sleep", no_sleep)
+ headers = chunk_headers(b"good", rows=1, has_more=False)
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ side_effect=[
+ httpx.Response(200, headers=headers, stream=BrokenSyncByteStream()),
+ chunk_response(b"good", rows=1, has_more=False),
+ ]
+ )
+ destination = BytesIO()
+
+ client.with_options(max_retries=0).audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ max_transfer_retries=1,
+ )
+
+ assert destination.getvalue() == b"good"
+ assert route.call_count == 2
+
+
+def test_download_retries_checksum_mismatch(
+ client: Kernel, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ def no_sleep(_: float) -> None:
+ pass
+
+ monkeypatch.setattr(audit_log_download.time, "sleep", no_sleep)
+ bad = chunk_response(b"bad", rows=1, has_more=False)
+ bad.headers["x-content-sha256"] = hashlib.sha256(b"good").hexdigest()
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ side_effect=[bad, chunk_response(b"good", rows=1, has_more=False)]
+ )
+ destination = BytesIO()
+
+ client.audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert destination.getvalue() == b"good"
+ assert route.call_count == 2
+
+
+def test_download_uses_client_http_retries(client: Kernel, respx_mock: MockRouter) -> None:
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ side_effect=[
+ httpx.Response(500, json={"message": "temporary failure"}),
+ chunk_response(b"good", rows=1, has_more=False),
+ ]
+ )
+
+ client.audit_logs.download(
+ to=BytesIO(),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert route.call_count == 2
+
+
+def test_download_respects_disabled_http_retries(client: Kernel, respx_mock: MockRouter) -> None:
+ route = respx_mock.get("/audit-logs/export/chunk").mock(
+ return_value=httpx.Response(500, json={"message": "temporary failure"})
+ )
+
+ with pytest.raises(InternalServerError, match="500"):
+ client.with_options(max_retries=0).audit_logs.download(
+ to=BytesIO(),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert route.call_count == 1
+
+
+def test_download_respects_transfer_retry_limit(client: Kernel, respx_mock: MockRouter) -> None:
+ bad = chunk_response(b"bad", rows=1, has_more=False)
+ bad.headers["x-content-sha256"] = hashlib.sha256(b"good").hexdigest()
+ route = respx_mock.get("/audit-logs/export/chunk").mock(return_value=bad)
+
+ with pytest.raises(AuditLogDownloadError, match="checksum mismatch"):
+ client.audit_logs.download(
+ to=BytesIO(),
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ max_transfer_retries=0,
+ )
+
+ assert route.call_count == 1
+
+
+def test_download_rejects_cursor_cycle(client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(
+ side_effect=[
+ chunk_response(b"first", rows=1, has_more=True, next_cursor="a"),
+ chunk_response(b"second", rows=1, has_more=True, next_cursor="b"),
+ chunk_response(b"duplicate", rows=1, has_more=True, next_cursor="a"),
+ ]
+ )
+ destination = BytesIO()
+
+ with pytest.raises(AuditLogDownloadError, match="response repeated X-Next-Cursor header"):
+ client.audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert destination.getvalue() == b"firstsecond"
+
+
+def test_download_rejects_invalid_cursor_before_writing(client: Kernel, respx_mock: MockRouter) -> None:
+ respx_mock.get("/audit-logs/export/chunk").mock(return_value=chunk_response(b"chunk", rows=1, has_more=True))
+ destination = BytesIO()
+
+ with pytest.raises(AuditLogDownloadError, match="response has invalid X-Next-Cursor header"):
+ client.audit_logs.download(
+ to=destination,
+ start="2026-06-01T00:00:00Z",
+ end="2026-06-02T00:00:00Z",
+ )
+
+ assert destination.getvalue() == b""
diff --git a/tests/test_browser_pool_acquire_response.py b/tests/test_browser_pool_acquire_response.py
new file mode 100644
index 00000000..269838a6
--- /dev/null
+++ b/tests/test_browser_pool_acquire_response.py
@@ -0,0 +1,70 @@
+import httpx
+import pytest
+
+from kernel import Kernel, AsyncKernel, NotFoundError, APIResponseValidationError
+
+
+def test_acquire_returns_browser() -> None:
+ browser = {
+ "cdp_ws_url": "wss://example.test/cdp",
+ "created_at": "2026-10-09T00:00:00Z",
+ "headless": True,
+ "memory": "1GiB",
+ "region": "us-east",
+ "session_id": "session-1",
+ "stealth": False,
+ "webdriver_ws_url": "wss://example.test/webdriver",
+ }
+ with Kernel(
+ api_key="test",
+ http_client=httpx.Client(transport=httpx.MockTransport(lambda _request: httpx.Response(200, json=browser))),
+ ) as client:
+ result = client.browser_pools.acquire("pool")
+ assert result is not None
+ assert result.session_id == "session-1"
+
+
+def test_acquire_returns_none_on_poll_timeout() -> None:
+ calls = 0
+
+ def handle(_request: httpx.Request) -> httpx.Response:
+ nonlocal calls
+ calls += 1
+ return httpx.Response(204)
+
+ with Kernel(api_key="test", http_client=httpx.Client(transport=httpx.MockTransport(handle))) as client:
+ assert client.browser_pools.acquire("pool") is None
+ response = client.browser_pools.with_raw_response.acquire("pool")
+ assert response.status_code == 204
+ assert response.parse() is None
+ assert response.parse(to=str) == ""
+ assert response.parse(to=bytes) == b""
+ assert response.parse(to=httpx.Response) is response.http_response
+ assert calls == 2
+
+
+async def test_async_acquire_returns_none_on_poll_timeout() -> None:
+ async with AsyncKernel(
+ api_key="test",
+ http_client=httpx.AsyncClient(transport=httpx.MockTransport(lambda _request: httpx.Response(204))),
+ ) as client:
+ assert await client.browser_pools.acquire("pool") is None
+
+
+def test_acquire_still_raises_not_found() -> None:
+ with Kernel(
+ api_key="test",
+ http_client=httpx.Client(transport=httpx.MockTransport(lambda _request: httpx.Response(404, json={}))),
+ ) as client:
+ with pytest.raises(NotFoundError):
+ client.browser_pools.acquire("missing")
+
+
+def test_unexpected_204_still_fails_strict_validation() -> None:
+ with Kernel(
+ api_key="test",
+ _strict_response_validation=True,
+ http_client=httpx.Client(transport=httpx.MockTransport(lambda _request: httpx.Response(204))),
+ ) as client:
+ with pytest.raises(APIResponseValidationError):
+ client.browser_pools.retrieve("pool")
diff --git a/tests/test_browser_routing.py b/tests/test_browser_routing.py
index 810de800..1908c688 100644
--- a/tests/test_browser_routing.py
+++ b/tests/test_browser_routing.py
@@ -1,13 +1,25 @@
from __future__ import annotations
+import io
import os
-from typing import Any, cast
+import asyncio
+from typing import Any, Iterable, Iterator, AsyncIterator, cast
+from pathlib import Path
+from typing_extensions import override
import httpx
import respx
import pytest
-from kernel import Kernel, AsyncKernel, InternalServerError
+from kernel import (
+ Kernel,
+ AsyncKernel,
+ APITimeoutError,
+ APIConnectionError,
+ AuthenticationError,
+ InternalServerError,
+ PermissionDeniedError,
+)
from kernel.lib.browser_routing.util import jwt_from_cdp_ws_url
from kernel.lib.browser_routing.routing import (
BrowserRoute,
@@ -15,6 +27,7 @@
browser_route_from_browser,
browser_routing_config_from_env,
)
+from kernel.types.browsers.f_upload_params import File as UploadFile
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = "sk-123"
@@ -29,10 +42,92 @@ def _fake_browser() -> dict[str, object]:
"created_at": "2020-01-01T00:00:00Z",
"headless": True,
"stealth": False,
+ "memory": "8GiB",
"timeout_seconds": 60,
+ "region": "us-east",
}
+def _skip_retry_sleep(_self: object, **_kwargs: object) -> None:
+ return None
+
+
+async def _skip_async_retry_sleep(_self: object, **_kwargs: object) -> None:
+ return None
+
+
+class _UnseekableFile(io.RawIOBase):
+ """A file-like upload body that cannot be rewound, e.g. a pipe.
+
+ `claims_seekable` reproduces a wrapper whose `seekable()` says True while
+ `seek()` still raises, which would otherwise render as an empty part.
+ """
+
+ name = "one.txt"
+
+ def __init__(self, content: bytes, *, claims_seekable: bool = False) -> None:
+ self._content = content
+ self._claims_seekable = claims_seekable
+
+ @override
+ def readable(self) -> bool:
+ return True
+
+ @override
+ def read(self, size: int = -1) -> bytes:
+ chunk = self._content if size < 0 else self._content[:size]
+ self._content = b"" if size < 0 else self._content[size:]
+ return chunk
+
+ @override
+ def tell(self) -> int:
+ return 0
+
+ @override
+ def seekable(self) -> bool:
+ return self._claims_seekable
+
+ @override
+ def seek(self, _offset: int, _whence: int = 0) -> int:
+ raise io.UnsupportedOperation("not seekable")
+
+
+class _UnreplayableAsyncBody:
+ def __init__(self, content: bytes) -> None:
+ self._content = content
+
+ def __aiter__(self) -> AsyncIterator[bytes]:
+ return self._iterate()
+
+ async def _iterate(self) -> AsyncIterator[bytes]:
+ if self._content:
+ content, self._content = self._content, b""
+ yield content
+
+
+class _NoSeekableAttrFile(io.RawIOBase):
+ """A file-like upload body whose `seek()` raises and reports no seekability."""
+
+ name = "one.txt"
+
+ def __init__(self, content: bytes) -> None:
+ self._content = content
+
+ @override
+ def readable(self) -> bool:
+ return True
+
+ @override
+ def read(self, size: int = -1) -> bytes:
+ chunk = self._content if size < 0 else self._content[:size]
+ self._content = b"" if size < 0 else self._content[size:]
+ return chunk
+
+ @override
+ def seek(self, _offset: int, _whence: int = 0) -> int:
+ raise OSError("not seekable")
+
+
def _cache_browser(client: Kernel) -> None:
route = browser_route_from_browser(_fake_browser())
assert route is not None
@@ -95,6 +190,140 @@ def test_browser_request_uses_curl_raw() -> None:
request = cast(httpx.Request, cast(Any, route.calls[0]).request)
assert "curl/raw" in str(request.url)
assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+ assert request.headers.get("x-kernel-direct-vm-request") is None
+
+
+def test_browser_stream_evicts_stale_route() -> None:
+ requests: list[httpx.Request] = []
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ if request.url.host == "browser-session.test":
+ return httpx.Response(401)
+ return httpx.Response(201)
+
+ http_client = httpx.Client(transport=httpx.MockTransport(handle_request))
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ with client.browsers.stream("sess-1", "POST", "https://example.com", content=[b"payload"]) as response:
+ assert response.status_code == 401
+ assert client.browser_route_cache.get("sess-1") is None
+ client.browsers.fs.write_file("sess-1", b"next", path="/tmp/x")
+
+ assert requests[0].url.host == "browser-session.test"
+ assert requests[0].headers.get("Authorization") is None
+ assert requests[1].url == httpx.URL(f"{base_url}/browsers/sess-1/fs/write_file?path=%2Ftmp%2Fx")
+ assert requests[1].headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@pytest.mark.asyncio
+async def test_async_browser_stream_evicts_stale_route() -> None:
+ requests: list[httpx.Request] = []
+
+ async def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ if request.url.host == "browser-session.test":
+ return httpx.Response(403)
+ return httpx.Response(201)
+
+ http_client = httpx.AsyncClient(transport=httpx.MockTransport(handle_request))
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ async with client.browsers.stream("sess-1", "POST", "https://example.com", content=b"payload") as response:
+ assert response.status_code == 403
+ assert client.browser_route_cache.get("sess-1") is None
+ await client.browsers.fs.write_file("sess-1", b"next", path="/tmp/x")
+
+ assert requests[0].url.host == "browser-session.test"
+ assert requests[0].headers.get("Authorization") is None
+ assert requests[1].url == httpx.URL(f"{base_url}/browsers/sess-1/fs/write_file?path=%2Ftmp%2Fx")
+ assert requests[1].headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@respx.mock
+def test_telemetry_stream_routes_directly_to_vm(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", "telemetry/stream")
+ route = respx.get("http://browser-session.test/browser/kernel/telemetry/stream").mock(
+ return_value=httpx.Response(
+ 200,
+ headers={"content-type": "text/event-stream"},
+ content=b'id: 1\ndata: {"category":"api"}\n\n',
+ )
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ stream = client.browsers.telemetry.stream("sess-1")
+ stream.close()
+
+ assert route.called
+ request = cast(httpx.Request, cast(Any, route.calls[0]).request)
+ assert request.url.path == "/browser/kernel/telemetry/stream"
+ assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+
+
+@pytest.mark.asyncio
+async def test_async_telemetry_stream_cancellation_reaches_transport(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.setenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", "telemetry/stream")
+ read_started = asyncio.Event()
+ transport_cancelled = asyncio.Event()
+ chunks: asyncio.Queue[bytes] = asyncio.Queue()
+
+ class BlockingSSEStream(httpx.AsyncByteStream):
+ @override
+ async def __aiter__(self) -> AsyncIterator[bytes]:
+ read_started.set()
+ try:
+ while True:
+ yield await chunks.get()
+ except asyncio.CancelledError:
+ transport_cancelled.set()
+ raise
+
+ @override
+ async def aclose(self) -> None:
+ pass
+
+ async def handle_request(_request: httpx.Request) -> httpx.Response:
+ return httpx.Response(
+ 200,
+ headers={"content-type": "text/event-stream"},
+ stream=BlockingSSEStream(),
+ )
+
+ http_client = httpx.AsyncClient(transport=httpx.MockTransport(handle_request))
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ stream = await client.browsers.telemetry.stream("sess-1")
+ consumer = asyncio.create_task(stream.__anext__())
+ await asyncio.wait_for(read_started.wait(), timeout=1)
+
+ consumer.cancel()
+ with pytest.raises(asyncio.CancelledError):
+ await asyncio.wait_for(consumer, timeout=1)
+ await asyncio.wait_for(transport_cancelled.wait(), timeout=1)
@respx.mock
@@ -176,11 +405,13 @@ async def test_async_raw_browser_create_warms_route_cache() -> None:
assert routed.content == b"ok"
request = cast(httpx.Request, cast(Any, routed_request.calls[0]).request)
assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+ assert request.headers.get("x-kernel-direct-vm-request") is None
@respx.mock
def test_only_browser_metadata_endpoints_warm_route_cache() -> None:
- projects_route = respx.get(f"{base_url}/projects").mock(return_value=httpx.Response(200, json=_fake_browser()))
+ projects_route = respx.get(f"{base_url}/org/projects").mock(return_value=httpx.Response(200, json=_fake_browser()))
with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
response = client.projects.with_raw_response.list()
with pytest.raises(ValueError, match="route cache"):
@@ -313,11 +544,1592 @@ def test_browser_route_from_browser_requires_base_url_and_jwt() -> None:
assert browser_route_from_browser({**_fake_browser(), "cdp_ws_url": None}) is None
-def test_browser_routing_config_from_env_defaults_to_curl(monkeypatch: pytest.MonkeyPatch) -> None:
+def test_browser_routing_config_from_env_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
- assert browser_routing_config_from_env().subresources == ("curl",)
+ assert browser_routing_config_from_env().subresources == (
+ "curl",
+ "telemetry/stream",
+ "computer",
+ "playwright",
+ "process",
+ "fs",
+ "logs/stream",
+ )
+
+
+def test_direct_vm_routing_allowlist_segment_boundary() -> None:
+ # Pins the fix: telemetry/stream (live SSE) routes to the VM; telemetry/events
+ # (historical, served by the control plane from S2) does NOT; and a
+ # stream-prefixed-but-different path is not matched.
+ from kernel.lib.browser_routing.routing import _matches_direct_vm_prefix
+
+ prefixes = ("curl", "telemetry/stream", "computer", "playwright", "process", "fs", "logs/stream")
+ assert _matches_direct_vm_prefix("telemetry/stream", prefixes) is True
+ assert _matches_direct_vm_prefix("telemetry/stream/x", prefixes) is True
+ assert _matches_direct_vm_prefix("telemetry/events", prefixes) is False
+ assert _matches_direct_vm_prefix("telemetry/streaming-config", prefixes) is False
+ assert _matches_direct_vm_prefix("telemetry", prefixes) is False
+ assert _matches_direct_vm_prefix("curl/raw", prefixes) is True
+ assert _matches_direct_vm_prefix("computer/screenshot", prefixes) is True
+ assert _matches_direct_vm_prefix("playwright/execute", prefixes) is True
+ assert _matches_direct_vm_prefix("process/exec", prefixes) is True
+ assert _matches_direct_vm_prefix("process/proc-1/stdout/stream", prefixes) is True
+ assert _matches_direct_vm_prefix("fs/read_file", prefixes) is True
+ assert _matches_direct_vm_prefix("fs/watch/watch-1/events", prefixes) is True
+ assert _matches_direct_vm_prefix("fsx/read_file", prefixes) is False
+ assert _matches_direct_vm_prefix("logs/stream", prefixes) is True
+ assert _matches_direct_vm_prefix("logs/stream/x", prefixes) is True
+ assert _matches_direct_vm_prefix("logs", prefixes) is False
+ assert _matches_direct_vm_prefix("logs/history", prefixes) is False
+ assert _matches_direct_vm_prefix("logstream", prefixes) is False
+ assert _matches_direct_vm_prefix("extensions", prefixes) is False
+ assert _matches_direct_vm_prefix("replays/rec-1", prefixes) is False
+
+
+def test_rewrite_direct_vm_options_keeps_telemetry_events_on_control_plane() -> None:
+ # Integration through the real routing hook: telemetry/events (historical,
+ # control-plane/S2) must NOT be rewritten to the VM, while telemetry/stream
+ # (live SSE) must be.
+ from kernel._models import FinalRequestOptions
+ from kernel.lib.browser_routing.routing import (
+ BrowserRoute,
+ BrowserRouteCache,
+ BrowserRoutingConfig,
+ rewrite_direct_vm_options,
+ )
+
+ cache = BrowserRouteCache()
+ cache.set(BrowserRoute(session_id="sess-1", base_url="http://browser-session.test/browser/kernel", jwt="token-abc"))
+ config = BrowserRoutingConfig(
+ subresources=("curl", "telemetry/stream", "computer", "playwright", "process", "fs", "logs/stream")
+ )
+
+ events = rewrite_direct_vm_options(
+ FinalRequestOptions(method="get", url="/browsers/sess-1/telemetry/events"), cache=cache, config=config
+ )
+ assert events.url == "/browsers/sess-1/telemetry/events" # unchanged -> control plane
+
+ stream = rewrite_direct_vm_options(
+ FinalRequestOptions(method="get", url="/browsers/sess-1/telemetry/stream"), cache=cache, config=config
+ )
+ assert str(stream.url).startswith("http://browser-session.test/browser/kernel/telemetry/stream")
+
+ screenshot = rewrite_direct_vm_options(
+ FinalRequestOptions(method="post", url="/browsers/sess-1/computer/screenshot"), cache=cache, config=config
+ )
+ assert str(screenshot.url).startswith("http://browser-session.test/browser/kernel/computer/screenshot")
+
+ execute = rewrite_direct_vm_options(
+ FinalRequestOptions(method="post", url="/browsers/sess-1/playwright/execute"), cache=cache, config=config
+ )
+ assert str(execute.url).startswith("http://browser-session.test/browser/kernel/playwright/execute")
+
+ process = rewrite_direct_vm_options(
+ FinalRequestOptions(method="post", url="/browsers/sess-1/process/exec"), cache=cache, config=config
+ )
+ assert str(process.url).startswith("http://browser-session.test/browser/kernel/process/exec")
+
+ fs_read = rewrite_direct_vm_options(
+ FinalRequestOptions(method="get", url="/browsers/sess-1/fs/read_file"), cache=cache, config=config
+ )
+ assert str(fs_read.url).startswith("http://browser-session.test/browser/kernel/fs/read_file")
+
+ logs_stream = rewrite_direct_vm_options(
+ FinalRequestOptions(method="get", url="/browsers/sess-1/logs/stream"), cache=cache, config=config
+ )
+ assert str(logs_stream.url).startswith("http://browser-session.test/browser/kernel/logs/stream")
+
+ logs_root = rewrite_direct_vm_options(
+ FinalRequestOptions(method="get", url="/browsers/sess-1/logs"), cache=cache, config=config
+ )
+ assert logs_root.url == "/browsers/sess-1/logs"
+
+ logs_history = rewrite_direct_vm_options(
+ FinalRequestOptions(method="get", url="/browsers/sess-1/logs/history"), cache=cache, config=config
+ )
+ assert logs_history.url == "/browsers/sess-1/logs/history"
+
+ extensions = rewrite_direct_vm_options(
+ FinalRequestOptions(method="post", url="/browsers/sess-1/extensions"), cache=cache, config=config
+ )
+ assert extensions.url == "/browsers/sess-1/extensions"
+
+ replays = rewrite_direct_vm_options(
+ FinalRequestOptions(method="get", url="/browsers/sess-1/replays"), cache=cache, config=config
+ )
+ assert replays.url == "/browsers/sess-1/replays"
def test_browser_routing_config_from_env_empty_string_disables_routing(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", "")
assert browser_routing_config_from_env().subresources == ()
+
+
+@respx.mock
+def test_default_browser_subresources_route_to_vm(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ screenshot = respx.post("http://browser-session.test/browser/kernel/computer/screenshot").mock(
+ return_value=httpx.Response(200, content=b"png", headers={"content-type": "image/png"})
+ )
+ execute = respx.post("http://browser-session.test/browser/kernel/playwright/execute").mock(
+ return_value=httpx.Response(200, json={"success": True})
+ )
+ process = respx.post("http://browser-session.test/browser/kernel/process/exec").mock(
+ return_value=httpx.Response(200, json={"exit_code": 0, "stdout_b64": "", "stderr_b64": ""})
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.computer.capture_screenshot("sess-1")
+ out = client.browsers.playwright.execute("sess-1", code="return 1")
+ process_out = client.browsers.process.exec("sess-1", command="echo")
+
+ assert screenshot.called
+ screenshot_req = cast(httpx.Request, cast(Any, screenshot.calls[0]).request)
+ assert screenshot_req.url.params.get("jwt") == "token-abc"
+ assert screenshot_req.headers.get("Authorization") is None
+ assert execute.called
+ execute_req = cast(httpx.Request, cast(Any, execute.calls[0]).request)
+ assert execute_req.url.params.get("jwt") == "token-abc"
+ assert execute_req.headers.get("Authorization") is None
+ assert process.called
+ process_req = cast(httpx.Request, cast(Any, process.calls[0]).request)
+ assert process_req.url.params.get("jwt") == "token-abc"
+ assert process_req.headers.get("Authorization") is None
+ assert out.success is True
+ assert process_out.exit_code == 0
+
+
+@respx.mock
+def test_control_plane_subresources_stay_on_api_origin_by_default(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ events = respx.get(f"{base_url}/browsers/sess-1/telemetry/events").mock(return_value=httpx.Response(200, json=[]))
+ replays = respx.get(f"{base_url}/browsers/sess-1/replays").mock(return_value=httpx.Response(200, json=[]))
+ extensions = respx.post(f"{base_url}/browsers/sess-1/extensions").mock(return_value=httpx.Response(204))
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.telemetry.events("sess-1")
+ client.browsers.replays.list("sess-1")
+ client.browsers.load_extensions("sess-1", extensions=[{"name": "ext", "zip_file": b"zip"}])
+
+ assert events.called
+ assert replays.called
+ assert extensions.called
+ extensions_req = cast(httpx.Request, cast(Any, extensions.calls[0]).request)
+ assert extensions_req.headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@respx.mock
+def test_stale_direct_vm_jwt_evicts_cache_and_retries_control_plane(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ vm = respx.post("http://browser-session.test/browser/kernel/computer/screenshot").mock(
+ return_value=httpx.Response(401, text="Invalid JWT")
+ )
+ api = respx.post(f"{base_url}/browsers/sess-1/computer/screenshot").mock(
+ return_value=httpx.Response(200, content=b"png", headers={"content-type": "image/png"})
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.computer.capture_screenshot("sess-1")
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert vm.called
+ assert api.called
+ api_req = cast(httpx.Request, cast(Any, api.calls[0]).request)
+ assert api_req.headers.get("Authorization") == f"Bearer {api_key}"
+
+
+def test_stale_direct_vm_jwt_does_not_evict_refreshed_route() -> None:
+ from kernel.lib.browser_routing.routing import maybe_evict_browser_route_from_response
+
+ cache = BrowserRouteCache()
+ cache.set(
+ BrowserRoute(
+ session_id="sess-1",
+ base_url="http://browser-session.test/browser/kernel",
+ jwt="token-abc",
+ )
+ )
+ cache.set(
+ BrowserRoute(
+ session_id="sess-1",
+ base_url="http://browser-session.test/browser/kernel",
+ jwt="jwt-FRESH",
+ )
+ )
+ request = httpx.Request(
+ "POST",
+ "http://browser-session.test/browser/kernel/computer/screenshot?jwt=token-abc",
+ )
+ maybe_evict_browser_route_from_response(
+ httpx.Response(401, text="Invalid JWT", request=request),
+ cache=cache,
+ )
+ route = cache.get("sess-1")
+ assert route is not None
+ assert route.jwt == "jwt-FRESH"
+
+
+def test_stale_direct_vm_auth_retry_does_not_require_cached_route() -> None:
+ from kernel.lib.browser_routing.routing import should_retry_stale_direct_vm_auth
+
+ request = httpx.Request(
+ "POST",
+ "http://browser-session.test/browser/kernel/computer/screenshot?jwt=token-abc",
+ )
+ response = httpx.Response(401, text="Invalid JWT", request=request)
+ empty = BrowserRouteCache()
+ assert should_retry_stale_direct_vm_auth(response) is True
+ assert empty.get("sess-1") is None
+
+
+@respx.mock
+def test_fs_json_endpoints_route_to_vm(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ list_files = respx.get("http://browser-session.test/browser/kernel/fs/list_files").mock(
+ return_value=httpx.Response(200, json=[])
+ )
+ move = respx.put("http://browser-session.test/browser/kernel/fs/move").mock(return_value=httpx.Response(204))
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.fs.list_files("sess-1", path="/tmp")
+ client.browsers.fs.move("sess-1", dest_path="/tmp/b", src_path="/tmp/a")
+
+ list_req = cast(httpx.Request, cast(Any, list_files.calls[0]).request)
+ assert list_req.url.params.get("path") == "/tmp"
+ assert list_req.url.params.get("jwt") == "token-abc"
+ assert list_req.headers.get("Authorization") is None
+
+ move_req = cast(httpx.Request, cast(Any, move.calls[0]).request)
+ assert move_req.url.path == "/browser/kernel/fs/move"
+ assert move_req.url.params.get("jwt") == "token-abc"
+ assert move_req.headers.get("Authorization") is None
+
+
+@respx.mock
+def test_fs_read_file_routes_binary_response_from_vm(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ read_file = respx.get("http://browser-session.test/browser/kernel/fs/read_file").mock(
+ return_value=httpx.Response(200, content=b"\x00binary", headers={"content-type": "application/octet-stream"})
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ response = client.browsers.fs.read_file("sess-1", path="/tmp/x")
+
+ assert response.read() == b"\x00binary"
+ request = cast(httpx.Request, cast(Any, read_file.calls[0]).request)
+ assert request.url.params.get("path") == "/tmp/x"
+ assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+
+
+@respx.mock
+def test_fs_write_file_routes_binary_body_to_vm(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ write_file = respx.put("http://browser-session.test/browser/kernel/fs/write_file").mock(
+ return_value=httpx.Response(201)
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.fs.write_file("sess-1", b"\x00payload", path="/tmp/x", mode="600")
+
+ request = cast(httpx.Request, cast(Any, write_file.calls[0]).request)
+ assert request.content == b"\x00payload"
+ assert request.headers.get("content-type") == "application/octet-stream"
+ assert request.url.params.get("path") == "/tmp/x"
+ assert request.url.params.get("mode") == "600"
+ assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+
+
+@respx.mock
+def test_fs_upload_routes_indexed_multipart_to_vm(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ upload = respx.post("http://browser-session.test/browser/kernel/fs/upload").mock(return_value=httpx.Response(201))
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.fs.upload(
+ "sess-1",
+ files=[
+ {"dest_path": "/tmp/one", "file": b"one"},
+ {"dest_path": "/tmp/two", "file": b"two"},
+ ],
+ )
+
+ request = cast(httpx.Request, cast(Any, upload.calls[0]).request)
+ assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+ body = request.read()
+ assert b'name="files[0][dest_path]"' in body
+ assert b'name="files[0][file]"' in body
+ assert b'name="files[1][dest_path]"' in body
+ assert b'name="files[1][file]"' in body
+ assert b"files[][" not in body
+
+
+@pytest.mark.parametrize("container", ["tuple", "generator"])
+def test_fs_upload_accepts_iterable_files(monkeypatch: pytest.MonkeyPatch, container: str) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(201)
+
+ entries: tuple[UploadFile, ...] = ({"dest_path": "/tmp/one", "file": b"abc"},)
+ files: Iterable[UploadFile] = entries if container == "tuple" else iter(entries)
+ http_client = httpx.Client(transport=httpx.MockTransport(handle_request))
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ client.browsers.fs.upload("sess-1", files=files)
+
+ body = requests[0].content
+ assert b'name="files[0][dest_path]"' in body
+ assert b'name="files[0][file]"; filename="upload"' in body
+ assert b"\r\nabc\r\n" in body
+ assert b"\r\nb'abc'\r\n" not in body
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("container", ["tuple", "generator"])
+async def test_async_fs_upload_accepts_iterable_files(monkeypatch: pytest.MonkeyPatch, container: str) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+
+ async def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(201)
+
+ entries: tuple[UploadFile, ...] = ({"dest_path": "/tmp/one", "file": b"abc"},)
+ files: Iterable[UploadFile] = entries if container == "tuple" else iter(entries)
+ http_client = httpx.AsyncClient(transport=httpx.MockTransport(handle_request))
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ await client.browsers.fs.upload("sess-1", files=files)
+
+ body = requests[0].content
+ assert b'name="files[0][dest_path]"' in body
+ assert b'name="files[0][file]"; filename="upload"' in body
+ assert b"\r\nabc\r\n" in body
+ assert b"\r\nb'abc'\r\n" not in body
+
+
+@respx.mock
+def test_fs_watch_events_stream_routes_to_vm(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ events = respx.get("http://browser-session.test/browser/kernel/fs/watch/watch-1/events").mock(
+ return_value=httpx.Response(
+ 200,
+ headers={"content-type": "text/event-stream"},
+ content=b'data: {"type":"CREATE","path":"/tmp/x","is_dir":false,"name":"x"}\n\n',
+ )
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ stream = client.browsers.fs.watch.events("watch-1", id_or_name="sess-1")
+ first = next(iter(stream))
+ stream.close()
+
+ assert first.path == "/tmp/x"
+ request = cast(httpx.Request, cast(Any, events.calls[0]).request)
+ assert request.url.path == "/browser/kernel/fs/watch/watch-1/events"
+ assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+
+
+@respx.mock
+def test_logs_stream_routes_to_vm(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ logs = respx.get("http://browser-session.test/browser/kernel/logs/stream").mock(
+ return_value=httpx.Response(
+ 200,
+ headers={"content-type": "text/event-stream"},
+ content=b'data: {"event":"log","message":"hello","timestamp":"2020-01-01T00:00:00Z"}\n\n',
+ )
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ stream = client.browsers.logs.stream("sess-1", source="path", path="/var/log/x", follow=True)
+ first = next(iter(stream))
+ stream.close()
+
+ assert first.message == "hello"
+ request = cast(httpx.Request, cast(Any, logs.calls[0]).request)
+ assert request.url.path == "/browser/kernel/logs/stream"
+ assert request.url.params.get("source") == "path"
+ assert request.url.params.get("path") == "/var/log/x"
+ assert request.url.params.get("follow") == "true"
+ assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+
+
+@pytest.mark.asyncio
+async def test_async_logs_stream_cancellation_reaches_transport(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ read_started = asyncio.Event()
+ transport_cancelled = asyncio.Event()
+ chunks: asyncio.Queue[bytes] = asyncio.Queue()
+ requested: list[httpx.URL] = []
+
+ class BlockingSSEStream(httpx.AsyncByteStream):
+ @override
+ async def __aiter__(self) -> AsyncIterator[bytes]:
+ read_started.set()
+ try:
+ while True:
+ yield await chunks.get()
+ except asyncio.CancelledError:
+ transport_cancelled.set()
+ raise
+
+ @override
+ async def aclose(self) -> None:
+ pass
+
+ async def handle_request(request: httpx.Request) -> httpx.Response:
+ requested.append(request.url)
+ return httpx.Response(
+ 200,
+ headers={"content-type": "text/event-stream"},
+ stream=BlockingSSEStream(),
+ )
+
+ http_client = httpx.AsyncClient(transport=httpx.MockTransport(handle_request))
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ stream = await client.browsers.logs.stream("sess-1", source="supervisor", supervisor_process="chromium")
+ consumer = asyncio.create_task(stream.__anext__())
+ await asyncio.wait_for(read_started.wait(), timeout=1)
+
+ consumer.cancel()
+ with pytest.raises(asyncio.CancelledError):
+ await asyncio.wait_for(consumer, timeout=1)
+ await asyncio.wait_for(transport_cancelled.wait(), timeout=1)
+
+ assert requested
+ assert requested[0].path == "/browser/kernel/logs/stream"
+
+
+@respx.mock
+def test_stale_direct_vm_jwt_replays_buffered_fs_body_on_control_plane(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ vm = respx.put("http://browser-session.test/browser/kernel/fs/write_file").mock(
+ return_value=httpx.Response(401, text="Invalid JWT")
+ )
+ api = respx.put(f"{base_url}/browsers/sess-1/fs/write_file").mock(return_value=httpx.Response(201))
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.fs.write_file("sess-1", b"payload", path="/tmp/x")
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert vm.called
+ api_req = cast(httpx.Request, cast(Any, api.calls[0]).request)
+ assert api_req.content == b"payload"
+ assert api_req.url.params.get("path") == "/tmp/x"
+ assert api_req.url.params.get("jwt") is None
+ assert api_req.headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@respx.mock
+def test_stale_direct_vm_jwt_does_not_replay_streamed_fs_body(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ vm = respx.put("http://browser-session.test/browser/kernel/fs/write_file").mock(
+ return_value=httpx.Response(401, text="Invalid JWT")
+ )
+ api = respx.put(f"{base_url}/browsers/sess-1/fs/write_file").mock(return_value=httpx.Response(201))
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ with pytest.raises(AuthenticationError):
+ client.browsers.fs.write_file("sess-1", iter([b"chunk-one", b"chunk-two"]), path="/tmp/x")
+ # The stale route is still evicted, so the caller's next attempt uses the control plane.
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert vm.call_count == 1
+ assert not api.called
+
+
+@pytest.mark.asyncio
+@respx.mock
+async def test_async_stale_direct_vm_jwt_does_not_replay_streamed_fs_body(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.AsyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ vm = respx.put("http://browser-session.test/browser/kernel/fs/write_file").mock(
+ return_value=httpx.Response(401, text="Invalid JWT")
+ )
+ api = respx.put(f"{base_url}/browsers/sess-1/fs/write_file").mock(return_value=httpx.Response(201))
+
+ async def _chunks() -> AsyncIterator[bytes]:
+ yield b"chunk-one"
+ yield b"chunk-two"
+
+ async with AsyncKernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(AuthenticationError):
+ await client.browsers.fs.write_file("sess-1", _chunks(), path="/tmp/x")
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert vm.call_count == 1
+ assert not api.called
+
+
+@respx.mock
+def test_stale_direct_vm_jwt_replays_multipart_upload_on_control_plane(
+ monkeypatch: pytest.MonkeyPatch,
+ tmp_path: Path,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ vm = respx.post("http://browser-session.test/browser/kernel/fs/upload").mock(
+ return_value=httpx.Response(403, text="Invalid JWT")
+ )
+ api = respx.post(f"{base_url}/browsers/sess-1/fs/upload").mock(return_value=httpx.Response(201))
+ upload = tmp_path / "one.txt"
+ upload.write_bytes(b"file-bytes")
+
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ with upload.open("rb") as handle:
+ client.browsers.fs.upload("sess-1", files=[{"dest_path": "/tmp/one", "file": handle}])
+
+ assert vm.called
+ api_req = cast(httpx.Request, cast(Any, api.calls[0]).request)
+ body = api_req.read()
+ assert b"file-bytes" in body
+ assert b'name="files[0][dest_path]"' in body
+ assert api_req.headers.get("Authorization") == f"Bearer {api_key}"
+
+
+def test_direct_vm_request_body_is_replayable_classification(tmp_path: Path) -> None:
+ from kernel.lib.browser_routing.routing import direct_vm_request_body_is_replayable
+
+ assert direct_vm_request_body_is_replayable(httpx.Request("GET", "http://vm.test/fs/read_file")) is True
+ assert direct_vm_request_body_is_replayable(httpx.Request("PUT", "http://vm.test/fs/write_file", content=b"x"))
+ assert (
+ direct_vm_request_body_is_replayable(httpx.Request("PUT", "http://vm.test/fs/write_file", content=iter([b"x"])))
+ is False
+ )
+
+ path = tmp_path / "one.txt"
+ path.write_bytes(b"file-bytes")
+ with path.open("rb") as handle:
+ seekable = httpx.Request(
+ "POST",
+ "http://vm.test/fs/upload",
+ data={"files[0][dest_path]": "/tmp/one"},
+ files=[("files[0][file]", handle)],
+ )
+ assert direct_vm_request_body_is_replayable(seekable) is True
+
+ unseekable = httpx.Request(
+ "POST",
+ "http://vm.test/fs/upload",
+ files=[("files[0][file]", cast(Any, _UnseekableFile(b"file-bytes")))],
+ )
+ assert direct_vm_request_body_is_replayable(unseekable) is False
+
+ # seekable() is not proof: the rewind itself has to succeed.
+ lies_about_seekable = httpx.Request(
+ "POST",
+ "http://vm.test/fs/upload",
+ files=[("files[0][file]", cast(Any, _UnseekableFile(b"file-bytes", claims_seekable=True)))],
+ )
+ assert direct_vm_request_body_is_replayable(lies_about_seekable) is False
+
+ without_seekable_attr = httpx.Request(
+ "POST",
+ "http://vm.test/fs/upload",
+ files=[("files[0][file]", cast(Any, _NoSeekableAttrFile(b"file-bytes")))],
+ )
+ assert direct_vm_request_body_is_replayable(without_seekable_attr) is False
+
+ in_memory = io.BytesIO(b"file-bytes")
+ buffered = httpx.Request("POST", "http://vm.test/fs/upload", files=[("files[0][file]", in_memory)])
+ assert direct_vm_request_body_is_replayable(buffered) is True
+
+ in_memory.close()
+ assert direct_vm_request_body_is_replayable(buffered) is False
+
+
+@respx.mock
+def test_env_override_can_exclude_fs_and_logs(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", "computer")
+ fs_read = respx.get(f"{base_url}/browsers/sess-1/fs/read_file").mock(
+ return_value=httpx.Response(200, content=b"x", headers={"content-type": "application/octet-stream"})
+ )
+ logs = respx.get(f"{base_url}/browsers/sess-1/logs/stream").mock(
+ return_value=httpx.Response(
+ 200,
+ headers={"content-type": "text/event-stream"},
+ content=b'data: {"event":"log","message":"hello","timestamp":"2020-01-01T00:00:00Z"}\n\n',
+ )
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.fs.read_file("sess-1", path="/tmp/x")
+ client.browsers.logs.stream("sess-1", source="path", path="/var/log/x").close()
+
+ assert fs_read.called
+ assert logs.called
+
+
+@respx.mock
+def test_empty_env_disables_fs_and_logs_routing(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", "")
+ fs_read = respx.get(f"{base_url}/browsers/sess-1/fs/read_file").mock(
+ return_value=httpx.Response(200, content=b"x", headers={"content-type": "application/octet-stream"})
+ )
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ response = client.browsers.fs.read_file("sess-1", path="/tmp/x")
+
+ assert fs_read.called
+ request = cast(httpx.Request, cast(Any, fs_read.calls[0]).request)
+ assert request.url.params.get("jwt") is None
+ assert request.headers.get("Authorization") == f"Bearer {api_key}"
+ assert response.read() == b"x"
+
+
+@respx.mock
+def test_stale_direct_vm_jwt_does_not_replay_multipart_that_cannot_rewind(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ vm = respx.post("http://browser-session.test/browser/kernel/fs/upload").mock(
+ return_value=httpx.Response(401, text="Invalid JWT")
+ )
+ api = respx.post(f"{base_url}/browsers/sess-1/fs/upload").mock(return_value=httpx.Response(201))
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ with pytest.raises(AuthenticationError):
+ client.browsers.fs.upload(
+ "sess-1",
+ files=[
+ {
+ "dest_path": "/tmp/one",
+ "file": cast(Any, _UnseekableFile(b"file-bytes", claims_seekable=True)),
+ }
+ ],
+ )
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert vm.call_count == 1
+ assert not api.called
+
+
+@pytest.mark.asyncio
+@respx.mock
+async def test_async_stale_direct_vm_jwt_does_not_replay_multipart_that_cannot_rewind(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.AsyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ vm = respx.post("http://browser-session.test/browser/kernel/fs/upload").mock(
+ return_value=httpx.Response(403, text="Invalid JWT")
+ )
+ api = respx.post(f"{base_url}/browsers/sess-1/fs/upload").mock(return_value=httpx.Response(201))
+ async with AsyncKernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(PermissionDeniedError):
+ await client.browsers.fs.upload(
+ "sess-1",
+ files=[
+ {
+ "dest_path": "/tmp/one",
+ "file": cast(Any, _UnseekableFile(b"file-bytes", claims_seekable=True)),
+ }
+ ],
+ )
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert vm.call_count == 1
+ assert not api.called
+
+
+@respx.mock
+def test_stale_direct_vm_jwt_evicts_route_without_retries_for_buffered_body(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ vm = respx.put("http://browser-session.test/browser/kernel/fs/write_file").mock(
+ return_value=httpx.Response(401, text="Invalid JWT")
+ )
+ api = respx.put(f"{base_url}/browsers/sess-1/fs/write_file").mock(return_value=httpx.Response(201))
+ with Kernel(base_url=base_url, api_key=api_key, max_retries=0, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ with pytest.raises(AuthenticationError):
+ client.browsers.fs.write_file("sess-1", b"payload", path="/tmp/x")
+ assert client.browser_route_cache.get("sess-1") is None
+
+ # The caller's next attempt goes to the control plane.
+ client.browsers.fs.write_file("sess-1", b"payload", path="/tmp/x")
+
+ assert vm.call_count == 1
+ api_req = cast(httpx.Request, cast(Any, api.calls[0]).request)
+ assert api_req.content == b"payload"
+ assert api_req.url.params.get("jwt") is None
+ assert api_req.headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@pytest.mark.asyncio
+@respx.mock
+async def test_async_stale_direct_vm_jwt_evicts_route_without_retries_for_streamed_body(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ vm = respx.put("http://browser-session.test/browser/kernel/fs/write_file").mock(
+ return_value=httpx.Response(403, text="Invalid JWT")
+ )
+ api = respx.put(f"{base_url}/browsers/sess-1/fs/write_file").mock(return_value=httpx.Response(201))
+
+ async def _chunks() -> AsyncIterator[bytes]:
+ yield b"chunk-one"
+
+ async with AsyncKernel(
+ base_url=base_url, api_key=api_key, max_retries=0, _strict_response_validation=True
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(PermissionDeniedError):
+ await client.browsers.fs.write_file("sess-1", _chunks(), path="/tmp/x")
+ assert client.browser_route_cache.get("sess-1") is None
+
+ await client.browsers.fs.write_file("sess-1", b"payload", path="/tmp/x")
+
+ assert vm.call_count == 1
+ api_req = cast(httpx.Request, cast(Any, api.calls[0]).request)
+ assert api_req.url.params.get("jwt") is None
+ assert api_req.headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@respx.mock
+def test_load_extensions_multipart_encoding_is_unchanged(monkeypatch: pytest.MonkeyPatch) -> None:
+ # Indexed names are scoped to fs.upload; every other multipart endpoint keeps
+ # the client's generic array encoding.
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ extensions = respx.post(f"{base_url}/browsers/sess-1/extensions").mock(return_value=httpx.Response(204))
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ _cache_browser(client)
+ client.browsers.load_extensions(
+ "sess-1",
+ extensions=[
+ {"name": "one", "zip_file": b"zip-one"},
+ {"name": "two", "zip_file": b"zip-two"},
+ ],
+ )
+
+ body = cast(httpx.Request, cast(Any, extensions.calls[0]).request).read()
+ assert b'name="extensions[][name]"' in body
+ assert b'name="extensions[][zip_file]"' in body
+ assert b"extensions[0]" not in body
+
+
+def test_generic_multipart_array_encoding_is_unchanged() -> None:
+ from kernel._models import FinalRequestOptions
+
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ request = client._build_request( # pyright: ignore[reportPrivateUsage]
+ FinalRequestOptions.construct(
+ method="post",
+ url="/foo",
+ headers={"Content-Type": "multipart/form-data; boundary=abc"},
+ json_data={"array": ["foo", "bar"]},
+ files=[("foo.txt", b"hello world")],
+ )
+ )
+
+ body = request.read()
+ assert b'name="array[]"' in body
+ assert b'name="array[0]"' not in body
+
+
+def test_indexed_multipart_body_flattens_only_given_values() -> None:
+ from kernel._types import omit
+ from kernel.lib.multipart import indexed_multipart_body
+
+ assert indexed_multipart_body(
+ {
+ "files": [
+ {"dest_path": "/tmp/one", "mode": omit},
+ {"dest_path": "/tmp/two"},
+ ],
+ "flag": True,
+ "skipped": omit,
+ }
+ ) == {
+ "files[0][dest_path]": "/tmp/one",
+ "files[1][dest_path]": "/tmp/two",
+ "flag": True,
+ }
+
+
+class _FailingSyncStream(httpx.SyncByteStream):
+ """A response body that fails while it is being read."""
+
+ def __init__(self, error_type: type[httpx.TransportError] = httpx.ReadError) -> None:
+ self.error_type = error_type
+
+ @override
+ def __iter__(self) -> Iterator[bytes]:
+ raise self.error_type("connection failed while reading the error body")
+
+
+class _FailingAsyncStream(httpx.AsyncByteStream):
+ def __init__(self, error_type: type[httpx.TransportError] = httpx.ReadError) -> None:
+ self.error_type = error_type
+
+ @override
+ async def __aiter__(self) -> AsyncIterator[bytes]:
+ raise self.error_type("connection failed while reading the error body")
+ yield b"" # pragma: no cover - unreachable, keeps this an async generator
+
+
+def test_stale_direct_vm_jwt_evicts_route_when_error_body_read_fails(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ if "browser-session.test" in str(request.url):
+ return httpx.Response(401, stream=_FailingSyncStream(), headers={"content-type": "text/plain"})
+ return httpx.Response(200, content=b"png", headers={"content-type": "image/png"})
+
+ http_client = httpx.Client(transport=httpx.MockTransport(handle_request))
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ max_retries=0,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ with pytest.raises(APIConnectionError):
+ client.browsers.computer.capture_screenshot("sess-1")
+ # The status was known before the body read failed, so the dead route is gone.
+ assert client.browser_route_cache.get("sess-1") is None
+
+ client.browsers.computer.capture_screenshot("sess-1")
+
+ assert str(requests[0].url).startswith("http://browser-session.test/browser/kernel/computer/screenshot")
+ assert requests[1].url == httpx.URL(f"{base_url}/browsers/sess-1/computer/screenshot")
+ assert requests[1].url.params.get("jwt") is None
+ assert requests[1].headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@pytest.mark.asyncio
+async def test_async_stale_direct_vm_jwt_evicts_route_when_error_body_read_fails(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+
+ async def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ if "browser-session.test" in str(request.url):
+ return httpx.Response(403, stream=_FailingAsyncStream(), headers={"content-type": "text/plain"})
+ return httpx.Response(200, content=b"png", headers={"content-type": "image/png"})
+
+ http_client = httpx.AsyncClient(transport=httpx.MockTransport(handle_request))
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ max_retries=0,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(APIConnectionError):
+ await client.browsers.computer.capture_screenshot("sess-1")
+ assert client.browser_route_cache.get("sess-1") is None
+
+ await client.browsers.computer.capture_screenshot("sess-1")
+
+ assert str(requests[0].url).startswith("http://browser-session.test/browser/kernel/computer/screenshot")
+ assert requests[1].url == httpx.URL(f"{base_url}/browsers/sess-1/computer/screenshot")
+ assert requests[1].url.params.get("jwt") is None
+ assert requests[1].headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@pytest.mark.parametrize(
+ ("error_type", "expected_error"),
+ [(httpx.ReadError, APIConnectionError), (httpx.ReadTimeout, APITimeoutError)],
+)
+def test_stale_direct_vm_auth_body_read_failure_does_not_retry_unreplayable_write(
+ monkeypatch: pytest.MonkeyPatch,
+ error_type: type[httpx.TransportError],
+ expected_error: type[Exception],
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ body = b"".join(cast(Iterator[bytes], request.stream))
+ requests.append((request.url, body))
+ if "browser-session.test" in str(request.url):
+ return httpx.Response(401, stream=_FailingSyncStream(error_type), headers={"content-type": "text/plain"})
+ return httpx.Response(201)
+
+ def read_request(request: httpx.Request) -> None:
+ request.read()
+
+ class Transport(httpx.BaseTransport):
+ @override
+ def handle_request(self, request: httpx.Request) -> httpx.Response:
+ return handle_request(request)
+
+ http_client = httpx.Client(transport=Transport(), event_hooks={"request": [read_request]})
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ with pytest.raises(expected_error):
+ client.browsers.fs.write_file("sess-1", _UnseekableFile(b"payload"), path="/tmp/x")
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+ assert client.browser_route_cache.get("sess-1") is None
+
+ client.browsers.fs.write_file("sess-1", b"next", path="/tmp/x")
+
+ assert requests[1] == (httpx.URL(f"{base_url}/browsers/sess-1/fs/write_file?path=%2Ftmp%2Fx"), b"next")
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+ ("error_type", "expected_error"),
+ [(httpx.ReadError, APIConnectionError), (httpx.ReadTimeout, APITimeoutError)],
+)
+async def test_async_stale_direct_vm_auth_body_read_failure_does_not_retry_unreplayable_write(
+ monkeypatch: pytest.MonkeyPatch,
+ error_type: type[httpx.TransportError],
+ expected_error: type[Exception],
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.AsyncAPIClient._sleep_for_retry", _skip_async_retry_sleep)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ async def handle_request(request: httpx.Request) -> httpx.Response:
+ body = b"".join([chunk async for chunk in cast(AsyncIterator[bytes], request.stream)])
+ requests.append((request.url, body))
+ if "browser-session.test" in str(request.url):
+ return httpx.Response(403, stream=_FailingAsyncStream(error_type), headers={"content-type": "text/plain"})
+ return httpx.Response(201)
+
+ async def read_request(request: httpx.Request) -> None:
+ await request.aread()
+
+ async def payload() -> AsyncIterator[bytes]:
+ yield b"payload"
+
+ class Transport(httpx.AsyncBaseTransport):
+ @override
+ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
+ return await handle_request(request)
+
+ http_client = httpx.AsyncClient(transport=Transport(), event_hooks={"request": [read_request]})
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(expected_error):
+ await client.browsers.fs.write_file("sess-1", cast(Any, payload()), path="/tmp/x")
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+ assert client.browser_route_cache.get("sess-1") is None
+
+ await client.browsers.fs.write_file("sess-1", b"next", path="/tmp/x")
+
+ assert requests[1] == (httpx.URL(f"{base_url}/browsers/sess-1/fs/write_file?path=%2Ftmp%2Fx"), b"next")
+
+
+@pytest.mark.parametrize(
+ ("failure_type", "expected_error"),
+ [
+ (httpx.ReadError, APIConnectionError),
+ (httpx.ReadTimeout, APITimeoutError),
+ (None, InternalServerError),
+ ],
+)
+def test_direct_vm_failure_does_not_retry_unreplayable_write(
+ monkeypatch: pytest.MonkeyPatch,
+ failure_type: type[httpx.TransportError] | None,
+ expected_error: type[Exception],
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ class Transport(httpx.BaseTransport):
+ @override
+ def handle_request(self, request: httpx.Request) -> httpx.Response:
+ body = b"".join(cast(Iterator[bytes], request.stream))
+ requests.append((request.url, body))
+ if failure_type is not None:
+ raise failure_type("connection failed after sending the request", request=request)
+ return httpx.Response(500, json={"error": "boom"})
+
+ http_client = httpx.Client(transport=Transport())
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ with pytest.raises(expected_error):
+ client.browsers.fs.write_file("sess-1", _UnseekableFile(b"payload"), path="/tmp/x")
+
+ assert client.browser_route_cache.get("sess-1") is not None
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+ ("failure_type", "expected_error"),
+ [
+ (httpx.ReadError, APIConnectionError),
+ (httpx.ReadTimeout, APITimeoutError),
+ (None, InternalServerError),
+ ],
+)
+async def test_async_direct_vm_failure_does_not_retry_unreplayable_write(
+ monkeypatch: pytest.MonkeyPatch,
+ failure_type: type[httpx.TransportError] | None,
+ expected_error: type[Exception],
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.AsyncAPIClient._sleep_for_retry", _skip_async_retry_sleep)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ async def payload() -> AsyncIterator[bytes]:
+ yield b"payload"
+
+ class Transport(httpx.AsyncBaseTransport):
+ @override
+ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
+ body = b"".join([chunk async for chunk in cast(AsyncIterator[bytes], request.stream)])
+ requests.append((request.url, body))
+ if failure_type is not None:
+ raise failure_type("connection failed after sending the request", request=request)
+ return httpx.Response(500, json={"error": "boom"})
+
+ http_client = httpx.AsyncClient(transport=Transport())
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(expected_error):
+ await client.browsers.fs.write_file("sess-1", cast(Any, payload()), path="/tmp/x")
+
+ assert client.browser_route_cache.get("sess-1") is not None
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+
+
+@pytest.mark.parametrize("status_code", [307, 308])
+def test_direct_vm_redirect_does_not_replay_unreplayable_write(
+ monkeypatch: pytest.MonkeyPatch,
+ status_code: int,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.SyncAPIClient._sleep_for_retry", _skip_retry_sleep)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ class Transport(httpx.BaseTransport):
+ @override
+ def handle_request(self, request: httpx.Request) -> httpx.Response:
+ body = b"".join(cast(Iterator[bytes], request.stream))
+ requests.append((request.url, body))
+ return httpx.Response(
+ status_code,
+ headers={"location": "http://other-vm.test/browser/kernel/fs/write_file"},
+ )
+
+ http_client = httpx.Client(transport=Transport(), follow_redirects=True)
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ with pytest.raises(APIConnectionError):
+ client.browsers.fs.write_file("sess-1", _UnseekableFile(b"payload"), path="/tmp/x")
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("status_code", [307, 308])
+async def test_async_direct_vm_redirect_does_not_replay_unreplayable_write(
+ monkeypatch: pytest.MonkeyPatch,
+ status_code: int,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ monkeypatch.setattr("kernel._base_client.AsyncAPIClient._sleep_for_retry", _skip_async_retry_sleep)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ class Transport(httpx.AsyncBaseTransport):
+ @override
+ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
+ body = b"".join([chunk async for chunk in cast(AsyncIterator[bytes], request.stream)])
+ requests.append((request.url, body))
+ return httpx.Response(
+ status_code,
+ headers={"location": "http://other-vm.test/browser/kernel/fs/write_file"},
+ )
+
+ http_client = httpx.AsyncClient(transport=Transport(), follow_redirects=True)
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(APIConnectionError):
+ await client.browsers.fs.write_file(
+ "sess-1",
+ cast(Any, _UnreplayableAsyncBody(b"payload")),
+ path="/tmp/x",
+ )
+ assert client.browser_route_cache.get("sess-1") is None
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+
+
+@pytest.mark.parametrize("origin_first", [True, False])
+@pytest.mark.parametrize(
+ ("status_code", "expected_error"),
+ [(307, APIConnectionError), (401, AuthenticationError)],
+)
+def test_direct_vm_failure_evicts_originating_cache_when_http_client_is_shared(
+ monkeypatch: pytest.MonkeyPatch,
+ origin_first: bool,
+ status_code: int,
+ expected_error: type[Exception],
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ class Transport(httpx.BaseTransport):
+ @override
+ def handle_request(self, request: httpx.Request) -> httpx.Response:
+ body = b"".join(cast(Iterator[bytes], request.stream))
+ requests.append((request.url, body))
+ return httpx.Response(
+ status_code,
+ headers={"location": "http://other-vm.test/browser/kernel/fs/write_file"},
+ )
+
+ http_client = httpx.Client(transport=Transport(), follow_redirects=True)
+
+ def make_client() -> Kernel:
+ return Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ )
+
+ if origin_first:
+ client, other_client = make_client(), make_client()
+ else:
+ other_client, client = make_client(), make_client()
+
+ try:
+ assert len(http_client.event_hooks["response"]) == 1
+ _cache_browser(client)
+ with pytest.raises(expected_error):
+ client.browsers.fs.write_file("sess-1", _UnseekableFile(b"payload"), path="/tmp/x")
+ assert client.browser_route_cache.get("sess-1") is None
+ assert other_client.browser_route_cache.get("sess-1") is None
+ finally:
+ http_client.close()
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("origin_first", [True, False])
+@pytest.mark.parametrize(
+ ("status_code", "expected_error"),
+ [(308, APIConnectionError), (403, PermissionDeniedError)],
+)
+async def test_async_direct_vm_failure_evicts_originating_cache_when_http_client_is_shared(
+ monkeypatch: pytest.MonkeyPatch,
+ origin_first: bool,
+ status_code: int,
+ expected_error: type[Exception],
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[tuple[httpx.URL, bytes]] = []
+
+ class Transport(httpx.AsyncBaseTransport):
+ @override
+ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
+ body = b"".join([chunk async for chunk in cast(AsyncIterator[bytes], request.stream)])
+ requests.append((request.url, body))
+ return httpx.Response(
+ status_code,
+ headers={"location": "http://other-vm.test/browser/kernel/fs/write_file"},
+ )
+
+ http_client = httpx.AsyncClient(transport=Transport(), follow_redirects=True)
+
+ def make_client() -> AsyncKernel:
+ return AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ )
+
+ if origin_first:
+ client, other_client = make_client(), make_client()
+ else:
+ other_client, client = make_client(), make_client()
+
+ try:
+ assert len(http_client.event_hooks["response"]) == 1
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ with pytest.raises(expected_error):
+ await client.browsers.fs.write_file(
+ "sess-1",
+ cast(Any, _UnreplayableAsyncBody(b"payload")),
+ path="/tmp/x",
+ )
+ assert client.browser_route_cache.get("sess-1") is None
+ assert other_client.browser_route_cache.get("sess-1") is None
+ finally:
+ await http_client.aclose()
+
+ assert requests == [
+ (
+ httpx.URL("http://browser-session.test/browser/kernel/fs/write_file?path=%2Ftmp%2Fx&jwt=token-abc"),
+ b"payload",
+ )
+ ]
+
+
+def test_direct_vm_auth_is_removed_after_httpx_and_request_hooks(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+
+ def add_auth(request: httpx.Request) -> None:
+ request.headers["Authorization"] = "Bearer from-hook"
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(200, json=[])
+
+ http_client = httpx.Client(
+ transport=httpx.MockTransport(handle_request),
+ auth=("user", "password"),
+ event_hooks={"request": [add_auth]},
+ )
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ client.browsers.fs.list_files("sess-1", path="/tmp")
+ client.browser_route_cache.delete("sess-1")
+ client.browsers.fs.list_files("sess-1", path="/tmp")
+
+ assert requests[0].headers.get("Authorization") is None
+ assert requests[0].headers.get("x-kernel-direct-vm-request") is None
+ assert requests[1].headers.get("Authorization") == "Bearer from-hook"
+
+
+@pytest.mark.asyncio
+async def test_async_direct_vm_auth_is_removed_after_httpx_and_request_hooks(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+
+ async def add_auth(request: httpx.Request) -> None:
+ request.headers["Authorization"] = "Bearer from-hook"
+
+ async def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(200, json=[])
+
+ http_client = httpx.AsyncClient(
+ transport=httpx.MockTransport(handle_request),
+ auth=("user", "password"),
+ event_hooks={"request": [add_auth]},
+ )
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ await client.browsers.fs.list_files("sess-1", path="/tmp")
+ client.browser_route_cache.delete("sess-1")
+ await client.browsers.fs.list_files("sess-1", path="/tmp")
+
+ assert requests[0].headers.get("Authorization") is None
+ assert requests[0].headers.get("x-kernel-direct-vm-request") is None
+ assert requests[1].headers.get("Authorization") == "Bearer from-hook"
+
+
+def test_direct_vm_preparation_survives_route_eviction(monkeypatch: pytest.MonkeyPatch) -> None:
+ from kernel.lib.browser_routing.routing import direct_vm_request_body_is_known_unreplayable
+
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[tuple[httpx.Request, bytes]] = []
+
+ class EvictingKernel(Kernel):
+ @override
+ def _prepare_request(self, request: httpx.Request) -> None:
+ self.browser_route_cache.delete("sess-1")
+ super()._prepare_request(request)
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ body = b"".join(cast(Iterator[bytes], request.stream))
+ requests.append((request, body))
+ return httpx.Response(201)
+
+ http_client = httpx.Client(transport=httpx.MockTransport(handle_request))
+ with EvictingKernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ client.browsers.fs.write_file("sess-1", _UnseekableFile(b"payload"), path="/tmp/x")
+
+ request, body = requests[0]
+ assert request.url.host == "browser-session.test"
+ assert request.url.params.get("jwt") == "token-abc"
+ assert request.headers.get("Authorization") is None
+ assert request.headers.get("x-kernel-direct-vm-request") is None
+ assert direct_vm_request_body_is_known_unreplayable(request)
+ assert body == b"payload"
+
+
+def test_copied_client_registers_one_direct_vm_hook_per_phase() -> None:
+ with Kernel(base_url=base_url, api_key=api_key, _strict_response_validation=True) as client:
+ copied = client.copy(api_key="sk-456")
+ assert copied.browser_route_cache is client.browser_route_cache
+ response_hooks = client._client.event_hooks["response"] # pyright: ignore[reportPrivateUsage]
+ request_hooks = client._client.event_hooks["request"] # pyright: ignore[reportPrivateUsage]
+ assert len(response_hooks) == 1
+ assert len(request_hooks) == 1
+
+
+def test_route_eviction_hook_runs_before_caller_response_hooks(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+ caller_hook_statuses: list[int] = []
+
+ def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ if "browser-session.test" in str(request.url):
+ return httpx.Response(401, stream=_FailingSyncStream(), headers={"content-type": "text/plain"})
+ return httpx.Response(200, content=b"png", headers={"content-type": "image/png"})
+
+ def caller_hook(response: httpx.Response) -> None:
+ caller_hook_statuses.append(response.status_code)
+ if response.status_code in {401, 403}:
+ # Reading a failing body raises out of the hook chain, which would
+ # skip any eviction hook registered after this one.
+ response.read()
+
+ http_client = httpx.Client(
+ transport=httpx.MockTransport(handle_request),
+ event_hooks={"response": [caller_hook]},
+ )
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ max_retries=0,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ _cache_browser(client)
+ assert http_client.event_hooks["response"][-1] is caller_hook
+ with pytest.raises(APIConnectionError):
+ client.browsers.computer.capture_screenshot("sess-1")
+ assert caller_hook_statuses == [401]
+ assert client.browser_route_cache.get("sess-1") is None
+
+ client.browsers.computer.capture_screenshot("sess-1")
+
+ assert str(requests[0].url).startswith("http://browser-session.test/browser/kernel/computer/screenshot")
+ assert requests[1].url == httpx.URL(f"{base_url}/browsers/sess-1/computer/screenshot")
+ assert requests[1].url.params.get("jwt") is None
+ assert requests[1].headers.get("Authorization") == f"Bearer {api_key}"
+
+
+@pytest.mark.asyncio
+async def test_async_route_eviction_hook_runs_before_caller_response_hooks(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("KERNEL_BROWSER_ROUTING_SUBRESOURCES", raising=False)
+ requests: list[httpx.Request] = []
+ caller_hook_statuses: list[int] = []
+
+ async def handle_request(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ if "browser-session.test" in str(request.url):
+ return httpx.Response(403, stream=_FailingAsyncStream(), headers={"content-type": "text/plain"})
+ return httpx.Response(200, content=b"png", headers={"content-type": "image/png"})
+
+ async def caller_hook(response: httpx.Response) -> None:
+ caller_hook_statuses.append(response.status_code)
+ if response.status_code in {401, 403}:
+ await response.aread()
+
+ http_client = httpx.AsyncClient(
+ transport=httpx.MockTransport(handle_request),
+ event_hooks={"response": [caller_hook]},
+ )
+ async with AsyncKernel(
+ base_url=base_url,
+ api_key=api_key,
+ max_retries=0,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ route = browser_route_from_browser(_fake_browser())
+ assert route is not None
+ client.browser_route_cache.set(route)
+ assert http_client.event_hooks["response"][-1] is caller_hook
+ with pytest.raises(APIConnectionError):
+ await client.browsers.computer.capture_screenshot("sess-1")
+ assert caller_hook_statuses == [403]
+ assert client.browser_route_cache.get("sess-1") is None
+
+ await client.browsers.computer.capture_screenshot("sess-1")
+
+ assert str(requests[0].url).startswith("http://browser-session.test/browser/kernel/computer/screenshot")
+ assert requests[1].url == httpx.URL(f"{base_url}/browsers/sess-1/computer/screenshot")
+ assert requests[1].url.params.get("jwt") is None
+ assert requests[1].headers.get("Authorization") == f"Bearer {api_key}"
+
+
+def test_route_eviction_hook_is_registered_once_before_caller_hooks() -> None:
+ def caller_hook(_response: httpx.Response) -> None: # pragma: no cover - never invoked
+ return None
+
+ http_client = httpx.Client(event_hooks={"response": [caller_hook]})
+ with Kernel(
+ base_url=base_url,
+ api_key=api_key,
+ http_client=http_client,
+ _strict_response_validation=True,
+ ) as client:
+ client.copy(api_key="sk-456")
+ hooks = http_client.event_hooks["response"]
+ assert len(hooks) == 2
+ assert hooks[1] is caller_hook
diff --git a/tests/test_config_registry_wait.py b/tests/test_config_registry_wait.py
new file mode 100644
index 00000000..e668aa8c
--- /dev/null
+++ b/tests/test_config_registry_wait.py
@@ -0,0 +1,181 @@
+from __future__ import annotations
+
+import asyncio
+from typing import Any
+from collections.abc import Iterator
+
+import httpx
+import pytest
+
+from kernel import Kernel, AsyncKernel
+from kernel._exceptions import KernelError
+
+
+def analysis_response(
+ status: str,
+ *,
+ analysis_id: str = "analysis-1",
+ finished_at: str | None = None,
+) -> dict[str, Any]:
+ return {
+ "analysis": {
+ "id": analysis_id,
+ "created_at": "2026-09-16T00:00:00Z",
+ "expires_at": "2026-09-16T00:45:00Z",
+ "failure": None,
+ "finished_at": finished_at,
+ "status": status,
+ "intent": None,
+ },
+ "recommendation": None,
+ "target": {
+ "domain": "example.com",
+ "host": "example.com",
+ "normalized": "https://example.com/",
+ },
+ "working_configurations": [],
+ "guidance": None,
+ "workload_outcome": None,
+ }
+
+
+def response_sequence(payloads: list[dict[str, Any]]) -> tuple[httpx.MockTransport, list[httpx.Request]]:
+ remaining: Iterator[dict[str, Any]] = iter(payloads)
+ requests: list[httpx.Request] = []
+
+ def handler(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ return httpx.Response(200, json=next(remaining))
+
+ return httpx.MockTransport(handler), requests
+
+
+def test_wait_for_result_polls_unknown_unfinished_status_until_finished() -> None:
+ transport, requests = response_sequence(
+ [
+ analysis_response("running"),
+ analysis_response("queued"),
+ analysis_response("archived", finished_at="2026-09-16T00:01:00Z"),
+ ]
+ )
+
+ with httpx.Client(transport=transport) as http_client:
+ client = Kernel(api_key="test", base_url="https://api.example", http_client=http_client)
+ result = client.config_registry.analyses.wait_for_result(
+ "analysis-1",
+ poll_interval=0.001,
+ extra_headers={"X-Test": "preserved", "X-Stainless-Poll-Helper": "caller"},
+ )
+
+ assert result.analysis is not None
+ assert result.analysis.status == "archived"
+ assert len(requests) == 3
+ assert all(request.headers["X-Test"] == "preserved" for request in requests)
+ assert all(request.headers["X-Stainless-Poll-Helper"] == "true" for request in requests)
+
+
+@pytest.mark.parametrize("status", ["completed", "failed", "canceled", "expired"])
+def test_wait_for_result_returns_known_terminal_status_without_finished_at(status: str) -> None:
+ transport, requests = response_sequence([analysis_response(status)])
+
+ with httpx.Client(transport=transport) as http_client:
+ client = Kernel(api_key="test", base_url="https://api.example", http_client=http_client)
+ result = client.config_registry.analyses.wait_for_result("analysis-1")
+
+ assert result.analysis is not None
+ assert result.analysis.status == status
+ assert len(requests) == 1
+
+
+def test_wait_for_result_zero_max_wait_reads_once_then_times_out() -> None:
+ transport, requests = response_sequence([analysis_response("running")])
+
+ with httpx.Client(transport=transport) as http_client:
+ client = Kernel(api_key="test", base_url="https://api.example", http_client=http_client)
+ with pytest.raises(TimeoutError, match="analysis-1"):
+ client.config_registry.analyses.wait_for_result("analysis-1", max_wait_seconds=0)
+
+ assert len(requests) == 1
+
+
+@pytest.mark.parametrize(
+ "payload, message",
+ [
+ ({"analysis": None}, "missing an analysis"),
+ (analysis_response("running", analysis_id="analysis-2"), "analysis-2"),
+ ],
+)
+def test_wait_for_result_rejects_invalid_analysis_response(payload: dict[str, Any], message: str) -> None:
+ transport, _ = response_sequence([payload])
+
+ with httpx.Client(transport=transport) as http_client:
+ client = Kernel(api_key="test", base_url="https://api.example", http_client=http_client)
+ with pytest.raises(KernelError, match=message):
+ client.config_registry.analyses.wait_for_result("analysis-1")
+
+
+def test_wait_for_result_rejects_missing_finished_at() -> None:
+ payload = analysis_response("running")
+ analysis = payload["analysis"]
+ assert isinstance(analysis, dict)
+ del analysis["finished_at"]
+ transport, _ = response_sequence([payload])
+
+ with httpx.Client(transport=transport) as http_client:
+ client = Kernel(api_key="test", base_url="https://api.example", http_client=http_client)
+ with pytest.raises(KernelError, match="finished_at"):
+ client.config_registry.analyses.wait_for_result("analysis-1")
+
+
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"poll_interval": 0},
+ {"poll_interval": float("nan")},
+ {"max_wait_seconds": -1},
+ {"max_wait_seconds": float("inf")},
+ ],
+)
+def test_wait_for_result_rejects_invalid_timing_options(kwargs: dict[str, float]) -> None:
+ client = Kernel(api_key="test", base_url="https://api.example")
+
+ with pytest.raises(ValueError):
+ client.config_registry.analyses.wait_for_result("analysis-1", **kwargs) # type: ignore[arg-type]
+
+
+async def test_async_wait_for_result_matches_sync_behavior() -> None:
+ transport, requests = response_sequence(
+ [
+ analysis_response("running"),
+ analysis_response("completed", finished_at="2026-09-16T00:01:00Z"),
+ ]
+ )
+
+ async with httpx.AsyncClient(transport=transport) as http_client:
+ client = AsyncKernel(api_key="test", base_url="https://api.example", http_client=http_client)
+ result = await client.config_registry.analyses.wait_for_result("analysis-1", poll_interval=0.001)
+
+ assert result.analysis is not None
+ assert result.analysis.status == "completed"
+ assert len(requests) == 2
+
+
+async def test_async_wait_for_result_is_cancellable_during_poll_sleep() -> None:
+ first_request = asyncio.Event()
+ requests: list[httpx.Request] = []
+
+ def handler(request: httpx.Request) -> httpx.Response:
+ requests.append(request)
+ first_request.set()
+ return httpx.Response(200, json=analysis_response("running"))
+
+ async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client:
+ client = AsyncKernel(api_key="test", base_url="https://api.example", http_client=http_client)
+ task = asyncio.create_task(client.config_registry.analyses.wait_for_result("analysis-1", poll_interval=60))
+ await first_request.wait()
+ await asyncio.sleep(0)
+ task.cancel()
+ with pytest.raises(asyncio.CancelledError):
+ await task
+
+ assert len(requests) == 1
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
new file mode 100644
index 00000000..cd59b3c6
--- /dev/null
+++ b/tests/test_pagination.py
@@ -0,0 +1,66 @@
+from typing import Any, List, Type, Union, Optional
+
+import httpx
+import pytest
+
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
+
+PageClass = Union[Type[SyncOffsetPagination[Any]], Type[AsyncOffsetPagination[Any]]]
+
+# build() and next_page_info() are plain synchronous methods on both classes,
+# so both generated variants are pinned against drifting apart on regeneration.
+both_classes = pytest.mark.parametrize("cls", [SyncOffsetPagination, AsyncOffsetPagination])
+
+
+def _page(cls: PageClass, *, items: List[Any], next_offset: Optional[int], has_more: Optional[bool]) -> Any:
+ headers: dict[str, str] = {}
+ if next_offset is not None:
+ headers["X-Next-Offset"] = str(next_offset)
+ if has_more is not None:
+ headers["X-Has-More"] = "true" if has_more else "false"
+ response = httpx.Response(200, headers=headers)
+ return cls.build(response=response, data=items)
+
+
+@both_classes
+def test_next_page_starts_at_exactly_x_next_offset(cls: PageClass) -> None:
+ # X-Next-Offset already holds the next page's start. Adding the current
+ # page length on top (the old behavior) skipped a full page per iteration.
+ page = _page(cls, items=[{}] * 100, next_offset=100, has_more=True)
+ info = page.next_page_info()
+ assert info is not None
+ assert info.params == {"offset": 100}
+
+
+@both_classes
+def test_stops_cleanly_when_last_page_omits_x_next_offset(cls: PageClass) -> None:
+ page = _page(cls, items=[{}] * 50, next_offset=None, has_more=False)
+ assert page.next_page_info() is None
+ assert page.has_next_page() is False
+
+
+@both_classes
+def test_stops_when_x_has_more_false(cls: PageClass) -> None:
+ page = _page(cls, items=[{}] * 50, next_offset=200, has_more=False)
+ assert page.has_next_page() is False
+
+
+@both_classes
+def test_stops_on_next_offset_zero_sentinel(cls: PageClass) -> None:
+ # 0 is the API's last-page sentinel. Assert via next_page_info directly,
+ # with has_more=True, so the has_more gate cannot mask the 0 handling.
+ page = _page(cls, items=[{}] * 50, next_offset=0, has_more=True)
+ assert page.next_page_info() is None
+
+
+@both_classes
+def test_stops_on_empty_page(cls: PageClass) -> None:
+ page = _page(cls, items=[], next_offset=300, has_more=True)
+ assert page.has_next_page() is False
+
+
+@both_classes
+def test_refuses_to_silently_truncate_on_contradictory_headers(cls: PageClass) -> None:
+ page = _page(cls, items=[{}] * 100, next_offset=None, has_more=True)
+ with pytest.raises(RuntimeError, match="refusing to silently truncate"):
+ page.has_next_page()
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index 4b8e4e48..c2ebd43d 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -26,6 +26,32 @@ def body() -> Iterator[bytes]:
await assert_empty_iter(iterator)
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_keepalive_comment_after_event_with_id(sync: bool, client: Kernel, async_client: AsyncKernel) -> None:
+ # A ``:`` comment frame (the server's SSE keepalive) that arrives after an event which set an
+ # id must be ignored, not dispatched as an empty event. last_event_id is sticky, so this is a
+ # regression guard against it leaking an undecodable empty frame into the typed stream.
+ def body() -> Iterator[bytes]:
+ yield b"id: 1\n"
+ yield b'data: {"foo":true}\n'
+ yield b"\n"
+ yield b":\n"
+ yield b"\n"
+ yield b'data: {"bar":false}\n'
+ yield b"\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.json() == {"foo": True}
+
+ sse = await iter_next(iterator)
+ assert sse.json() == {"bar": False}
+
+ await assert_empty_iter(iterator)
+
+
@pytest.mark.asyncio
@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
async def test_data_missing_event(sync: bool, client: Kernel, async_client: AsyncKernel) -> None:
diff --git a/tests/test_vault_operation_retries.py b/tests/test_vault_operation_retries.py
new file mode 100644
index 00000000..f25520e7
--- /dev/null
+++ b/tests/test_vault_operation_retries.py
@@ -0,0 +1,74 @@
+from __future__ import annotations
+
+import httpx
+import pytest
+
+from kernel import Kernel, AsyncKernel, APIStatusError, APIConnectionError
+
+
+@pytest.mark.parametrize("failure", ["connection", 409, 429, 500])
+def test_fill_does_not_retry(failure: str | int) -> None:
+ calls = 0
+
+ def handle(request: httpx.Request) -> httpx.Response:
+ nonlocal calls
+ calls += 1
+ if isinstance(failure, str):
+ raise httpx.ReadError("connection lost", request=request)
+ return httpx.Response(failure, json={})
+
+ with Kernel(
+ api_key="test", max_retries=1, http_client=httpx.Client(transport=httpx.MockTransport(handle))
+ ) as client:
+ with pytest.raises((APIConnectionError, APIStatusError)):
+ client.vaults.items.perform_operation(
+ "login",
+ id_or_name="vault",
+ type="fill",
+ browser_id="browser",
+ fields=[{"field": "password", "selector": "#password"}],
+ )
+ assert calls == 1
+ assert client.max_retries == 1
+
+
+@pytest.mark.parametrize("failure", ["connection", 409, 429, 500])
+async def test_async_fill_does_not_retry(failure: str | int) -> None:
+ calls = 0
+
+ def handle(request: httpx.Request) -> httpx.Response:
+ nonlocal calls
+ calls += 1
+ if isinstance(failure, str):
+ raise httpx.ReadError("connection lost", request=request)
+ return httpx.Response(failure, json={})
+
+ async with AsyncKernel(
+ api_key="test", max_retries=1, http_client=httpx.AsyncClient(transport=httpx.MockTransport(handle))
+ ) as client:
+ with pytest.raises((APIConnectionError, APIStatusError)):
+ await client.vaults.items.perform_operation(
+ "login",
+ id_or_name="vault",
+ type="fill",
+ browser_id="browser",
+ fields=[{"field": "password", "selector": "#password"}],
+ )
+ assert calls == 1
+ assert client.max_retries == 1
+
+
+def test_reads_keep_client_retries() -> None:
+ calls = 0
+
+ def handle(request: httpx.Request) -> httpx.Response:
+ nonlocal calls
+ calls += 1
+ raise httpx.ReadError("connection lost", request=request)
+
+ with Kernel(
+ api_key="test", max_retries=1, http_client=httpx.Client(transport=httpx.MockTransport(handle))
+ ) as client:
+ with pytest.raises(APIConnectionError):
+ client.vaults.items.retrieve("login", id_or_name="vault")
+ assert calls == 2