diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 43fd5a7..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,15 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/debian -{ - "name": "Development", - "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", - "features": { - "ghcr.io/devcontainers/features/node:1": {} - }, - "postCreateCommand": "yarn install", - "customizations": { - "vscode": { - "extensions": ["esbenp.prettier-vscode"] - } - } -} diff --git a/.fernignore b/.fernignore new file mode 100644 index 0000000..084a8eb --- /dev/null +++ b/.fernignore @@ -0,0 +1 @@ +# Specify files that shouldn't be modified by Fern diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93a1284..7f884ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,88 +1,30 @@ -name: CI -on: - push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'stl-preview-head/**' - - 'stl-preview-base/**' - pull_request: - branches-ignore: - - 'stl-preview-head/**' - - 'stl-preview-base/**' +name: ci -jobs: - lint: - timeout-minutes: 10 - name: lint - runs-on: ${{ github.repository == 'stainless-sdks/browser-use-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork - steps: - - uses: actions/checkout@v4 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '20' +on: [push] - - name: Bootstrap - run: ./scripts/bootstrap - - - name: Check types - run: ./scripts/lint +jobs: + compile: + runs-on: ubuntu-latest - build: - timeout-minutes: 5 - name: build - runs-on: ${{ github.repository == 'stainless-sdks/browser-use-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork - permissions: - contents: read - id-token: write steps: - - uses: actions/checkout@v4 + - name: Checkout repo + uses: actions/checkout@v4 - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '20' + - name: Set up node + uses: actions/setup-node@v3 - - name: Bootstrap - run: ./scripts/bootstrap + - name: Compile + run: yarn && yarn build - - name: Check build - run: ./scripts/build - - - name: Get GitHub OIDC Token - if: github.repository == 'stainless-sdks/browser-use-typescript' - id: github-oidc - uses: actions/github-script@v6 - with: - script: core.setOutput('github_token', await core.getIDToken()); - - - name: Upload tarball - if: github.repository == 'stainless-sdks/browser-use-typescript' - env: - URL: https://pkg.stainless.com/s - AUTH: ${{ steps.github-oidc.outputs.github_token }} - SHA: ${{ github.sha }} - run: ./scripts/utils/upload-artifact.sh test: - timeout-minutes: 10 - name: test - runs-on: ${{ github.repository == 'stainless-sdks/browser-use-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork - steps: - - uses: actions/checkout@v4 + runs-on: ubuntu-latest - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '20' + steps: + - name: Checkout repo + uses: actions/checkout@v4 - - name: Bootstrap - run: ./scripts/bootstrap + - name: Set up node + uses: actions/setup-node@v3 - - name: Run tests - run: ./scripts/test + - name: Compile + run: yarn && yarn test diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml deleted file mode 100644 index 8032602..0000000 --- a/.github/workflows/publish-npm.yml +++ /dev/null @@ -1,32 +0,0 @@ -# This workflow is triggered when a GitHub release is created. -# It can also be run manually to re-publish to NPM in case it failed for some reason. -# You can run this workflow by navigating to https://www.github.com/browser-use/browser-use-node/actions/workflows/publish-npm.yml -name: Publish NPM -on: - workflow_dispatch: - - release: - types: [published] - -jobs: - publish: - name: publish - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install dependencies - run: | - yarn install - - - name: Publish to NPM - run: | - bash ./bin/publish-npm - env: - NPM_TOKEN: ${{ secrets.BROWSER_USE_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml deleted file mode 100644 index 9a944dc..0000000 --- a/.github/workflows/release-doctor.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Release Doctor -on: - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - release_doctor: - name: release doctor - runs-on: ubuntu-latest - if: github.repository == 'browser-use/browser-use-node' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') - - steps: - - uses: actions/checkout@v4 - - - name: Check release environment - run: | - bash ./bin/check-release-environment - env: - NPM_TOKEN: ${{ secrets.BROWSER_USE_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 03f2361..72271e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,3 @@ -.prism.log node_modules -yarn-error.log -codegen.log -Brewfile.lock.json -dist -dist-deno -/*.tgz -.idea/ - -.env \ No newline at end of file +.DS_Store +/dist \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..383dd36 --- /dev/null +++ b/.npmignore @@ -0,0 +1,10 @@ +node_modules +src +tests +.gitignore +.github +.fernignore +.prettierrc.yml +tsconfig.json +yarn.lock +pnpm-lock.yaml \ No newline at end of file diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index dc0bb0f..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v22.12.0 diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 3548c5a..0000000 --- a/.prettierignore +++ /dev/null @@ -1,7 +0,0 @@ -CHANGELOG.md -/ecosystem-tests/*/** -/node_modules -/deno - -# don't format tsc output, will break source maps -/dist diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index af75ada..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "arrowParens": "always", - "experimentalTernaries": true, - "printWidth": 110, - "singleQuote": true, - "trailingComma": "all" -} diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..0c06786 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,2 @@ +tabWidth: 4 +printWidth: 120 diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index c3f1463..0000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "1.2.0" -} diff --git a/.stats.yml b/.stats.yml deleted file mode 100644 index 808f944..0000000 --- a/.stats.yml +++ /dev/null @@ -1,4 +0,0 @@ -configured_endpoints: 26 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browser-use%2Fbrowser-use-814bdd9f98b750d42a2b713a0a12b14fc5a0241ff820b2fbc7666ab2e9a5443f.yml -openapi_spec_hash: 0dae4d4d33a3ec93e470f9546e43fad3 -config_hash: dd3e22b635fa0eb9a7c741a8aaca2a7f diff --git a/Brewfile b/Brewfile deleted file mode 100644 index e4feee6..0000000 --- a/Brewfile +++ /dev/null @@ -1 +0,0 @@ -brew "node" diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c9b4c4b..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,157 +0,0 @@ -# Changelog - -## 1.2.0 (2025-08-22) - -Full Changelog: [v1.1.3...v1.2.0](https://github.com/browser-use/browser-use-node/compare/v1.1.3...v1.2.0) - -### Features - -* NextJS Utils ([b5e1006](https://github.com/browser-use/browser-use-node/commit/b5e10060a064de9a9454fa90e461f09154094742)) - -## 1.1.3 (2025-08-22) - -Full Changelog: [v1.1.2...v1.1.3](https://github.com/browser-use/browser-use-node/compare/v1.1.2...v1.1.3) - -### Chores - -* add package to package.json ([28293f9](https://github.com/browser-use/browser-use-node/commit/28293f9d1f3807dd8580450c5ba98a18392191be)) -* **client:** qualify global Blob ([944dd90](https://github.com/browser-use/browser-use-node/commit/944dd905151ef5030991f8dfd0168cf88d940068)) - -## 1.1.2 (2025-08-21) - -Full Changelog: [v1.1.1...v1.1.2](https://github.com/browser-use/browser-use-node/compare/v1.1.1...v1.1.2) - -### Bug Fixes - -* Improve Quick Start Section ([a1b0539](https://github.com/browser-use/browser-use-node/commit/a1b0539a5e04c9b31fd4f1e798d71dd5d02d1976)) - -## 1.1.1 (2025-08-20) - -Full Changelog: [v1.1.0...v1.1.1](https://github.com/browser-use/browser-use-node/compare/v1.1.0...v1.1.1) - -## 1.1.0 (2025-08-20) - -Full Changelog: [v1.0.0...v1.1.0](https://github.com/browser-use/browser-use-node/compare/v1.0.0...v1.1.0) - -### Features - -* LLM key strings over LLM model enum ([415d734](https://github.com/browser-use/browser-use-node/commit/415d734d7f1c8cec85d3aee9f0e1050f31a7b1ac)) -* **mcp:** add code execution tool ([96e6aca](https://github.com/browser-use/browser-use-node/commit/96e6acad04252d075543b60a8de59f95648ca8a7)) - -## 1.0.0 (2025-08-19) - -Full Changelog: [v0.7.0...v1.0.0](https://github.com/browser-use/browser-use-node/compare/v0.7.0...v1.0.0) - -### ⚠ BREAKING CHANGES - -* This PR creates a v1 release of the Browser Use Node SDK - -### Performance Improvements - -* Create 1.0.0 Release ([a2007b8](https://github.com/browser-use/browser-use-node/commit/a2007b806fac33b89092b8bebbc4bbe972de6c76)) - -## 0.7.0 (2025-08-19) - -Full Changelog: [v0.6.0...v0.7.0](https://github.com/browser-use/browser-use-node/compare/v0.6.0...v0.7.0) - -### Features - -* **api:** manual updates ([d339b83](https://github.com/browser-use/browser-use-node/commit/d339b83355a358c7a55fa501576403cb30e0aa31)) -* **api:** manual updates ([5d09807](https://github.com/browser-use/browser-use-node/commit/5d09807eb2cb8fa30555775d0a5cb21b2c7f3c7f)) - -## 0.6.0 (2025-08-19) - -Full Changelog: [v0.5.1...v0.6.0](https://github.com/browser-use/browser-use-node/compare/v0.5.1...v0.6.0) - -### Features - -* Event Verify Type Signature, Aggressively Push Status Checks in Webhook ([6239b3a](https://github.com/browser-use/browser-use-node/commit/6239b3a632df204ab01fcc89c40890d80d0c1bf6)) -* Improve Docs ([12429c9](https://github.com/browser-use/browser-use-node/commit/12429c9289030fc8c4c1bbb7fe5aa249c08ec8ee)) - - -### Bug Fixes - -* Trigger SDK build ([e5b9215](https://github.com/browser-use/browser-use-node/commit/e5b9215d158a2e50a736ba185dd641dece9d96bb)) -* Trigger SDK build ([0ce7b48](https://github.com/browser-use/browser-use-node/commit/0ce7b48d3920460c40091d0449413c7c59d4ef19)) - -## 0.5.1 (2025-08-18) - -Full Changelog: [v0.5.0...v0.5.1](https://github.com/browser-use/browser-use-node/compare/v0.5.0...v0.5.1) - -### Bug Fixes - -* Fix Browser Use CLI ([132bc96](https://github.com/browser-use/browser-use-node/commit/132bc96cdb1917c6d95b84382463b08d0e75096e)) - -## 0.5.0 (2025-08-18) - -Full Changelog: [v0.4.0...v0.5.0](https://github.com/browser-use/browser-use-node/compare/v0.4.0...v0.5.0) - -### Features - -* Add start_url ([10e4187](https://github.com/browser-use/browser-use-node/commit/10e4187e952398bb1bd7f1607a0450cca0e25b0f)) -* Align Task Filtering by Status with `status` Field ([1ea0943](https://github.com/browser-use/browser-use-node/commit/1ea0943b3cbca9fb9f40e36c33094756c979ac54)) - -## 0.4.0 (2025-08-17) - -Full Changelog: [v0.3.0...v0.4.0](https://github.com/browser-use/browser-use-node/compare/v0.3.0...v0.4.0) - -### Features - -* Update param and response views ([68615f4](https://github.com/browser-use/browser-use-node/commit/68615f4851b05203a78520c34409ad4c8f043cc4)) - - -### Chores - -* **deps:** update dependency @types/node to v20.17.58 ([42c532e](https://github.com/browser-use/browser-use-node/commit/42c532ede1ba630159e410ebf3c8cc8a73721242)) -* **internal:** formatting change ([e09c835](https://github.com/browser-use/browser-use-node/commit/e09c8357715abca8d1e095316a7f43d171a3a0f9)) - -## 0.3.0 (2025-08-15) - -Full Changelog: [v0.2.2...v0.3.0](https://github.com/browser-use/browser-use-node/compare/v0.2.2...v0.3.0) - -### Features - -* Fix Stainless GitHub Action ([16f3e7f](https://github.com/browser-use/browser-use-node/commit/16f3e7f6a43c5f29c81543624ca56cfd72b8e0cf)) - -## 0.2.2 (2025-08-15) - -Full Changelog: [v0.2.1...v0.2.2](https://github.com/browser-use/browser-use-node/compare/v0.2.1...v0.2.2) - -### Bug Fixes - -* stream completion ([2f6ce1f](https://github.com/browser-use/browser-use-node/commit/2f6ce1f1a312ba3f94872e262db2c17df5f6bf56)) - -## 0.2.1 (2025-08-15) - -Full Changelog: [v0.2.0...v0.2.1](https://github.com/browser-use/browser-use-node/compare/v0.2.0...v0.2.1) - -## 0.2.0 (2025-08-15) - -Full Changelog: [v0.1.1...v0.2.0](https://github.com/browser-use/browser-use-node/compare/v0.1.1...v0.2.0) - -### Features - -* **api:** api update ([3df1a94](https://github.com/browser-use/browser-use-node/commit/3df1a94275d67ce41756227e6f0b749b2c3ed009)) -* **api:** manual updates ([7183cef](https://github.com/browser-use/browser-use-node/commit/7183cef2c497b83985d368cb3a559fc0e11e4082)) - -## 0.1.1 (2025-08-14) - -Full Changelog: [v0.1.0...v0.1.1](https://github.com/browser-use/browser-use-node/compare/v0.1.0...v0.1.1) - -### Chores - -* **internal:** codegen related update ([cf1f8c5](https://github.com/browser-use/browser-use-node/commit/cf1f8c5e6f2ba3b1b4795ad0e5fd1e1eaba2c187)) - -## 0.1.0 (2025-08-09) - -Full Changelog: [v0.0.1...v0.1.0](https://github.com/browser-use/browser-use-node/compare/v0.0.1...v0.1.0) - -### Features - -* **api:** update via SDK Studio ([c56303c](https://github.com/browser-use/browser-use-node/commit/c56303c06357c1b24d6e797dd9a1fb7ca4e4249b)) - - -### Chores - -* update SDK settings ([e47a3c0](https://github.com/browser-use/browser-use-node/commit/e47a3c0111c16d7c1e7096a8b69f5e77c85f82fe)) -* update SDK settings ([c39de14](https://github.com/browser-use/browser-use-node/commit/c39de1490a0d59e65b376efa94ec959b87b43d47)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 6d7459c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,107 +0,0 @@ -## Setting up the environment - -This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). -Other package managers may work but are not officially supported for development. - -To set up the repository, run: - -```sh -$ yarn -$ yarn build -``` - -This will install all the required dependencies and build output files to `dist/`. - -## Modifying/Adding code - -Most of the SDK is generated code. Modifications to code will be persisted between generations, but may -result in merge conflicts between manual patches and changes from the generator. The generator will never -modify the contents of the `src/lib/` and `examples/` directories. - -## Adding and running examples - -All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. - -```ts -// add an example to examples/.ts - -#!/usr/bin/env -S npm run tsn -T -… -``` - -```sh -$ chmod +x examples/.ts -# run the example against your api -$ yarn tsn -T examples/.ts -``` - -## Using the repository from source - -If you’d like to use the repository from source, you can either install from git or link to a cloned repository: - -To install via git: - -```sh -$ npm install git+ssh://git@github.com:browser-use/browser-use-node.git -``` - -Alternatively, to link a local copy of the repo: - -```sh -# Clone -$ git clone https://www.github.com/browser-use/browser-use-node -$ cd browser-use-node - -# With yarn -$ yarn link -$ cd ../my-package -$ yarn link browser-use-sdk - -# With pnpm -$ pnpm link --global -$ cd ../my-package -$ pnpm link -—global browser-use-sdk -``` - -## Running tests - -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. - -```sh -$ npx prism mock path/to/your/openapi.yml -``` - -```sh -$ yarn run test -``` - -## Linting and formatting - -This repository uses [prettier](https://www.npmjs.com/package/prettier) and -[eslint](https://www.npmjs.com/package/eslint) to format the code in the repository. - -To lint: - -```sh -$ yarn lint -``` - -To format and fix all lint issues automatically: - -```sh -$ yarn fix -``` - -## Publishing and releases - -Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If -the changes aren't made through the automated pipeline, you may want to make releases manually. - -### Publish with a GitHub workflow - -You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/browser-use/browser-use-node/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. - -### Publish manually - -If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on -the environment. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 6eff678..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2025 Browser Use - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index 673e169..c71173d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -Browser Use JS +# BrowserUse TypeScript Library -```sh -pnpm add browser-use-sdk -``` +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=https%3A%2F%2Fgithub.com%2Fbrowser-use%2Fbrowser-use-node) +[![npm shield](https://img.shields.io/npm/v/)](https://www.npmjs.com/package/) + +The BrowserUse TypeScript library provides convenient access to the BrowserUse APIs from TypeScript. ## Two-Step QuickStart @@ -11,14 +12,14 @@ pnpm add browser-use-sdk 1. ✌️ Automate the web! ```ts -import BrowserUse from 'browser-use-sdk'; +import BrowserUse from "browser-use-sdk"; const client = new BrowserUse({ - apiKey: 'bu_...', + apiKey: "bu_...", }); const result = await client.tasks.run({ - task: 'Search for the top 10 Hacker News posts and return the title and url.', + task: "Search for the top 10 Hacker News posts and return the title and url.", }); console.log(result.doneOutput); @@ -29,23 +30,23 @@ console.log(result.doneOutput); ### Structured Output with Zod ```ts -import z from 'zod'; +import z from "zod"; const TaskOutput = z.object({ - posts: z.array( - z.object({ - title: z.string(), - url: z.string(), - }), - ), + posts: z.array( + z.object({ + title: z.string(), + url: z.string(), + }), + ), }); const result = await client.tasks.run({ - task: 'Search for the top 10 Hacker News posts and return the title and url.', + task: "Search for the top 10 Hacker News posts and return the title and url.", }); for (const post of result.parsedOutput.posts) { - console.log(`${post.title} - ${post.url}`); + console.log(`${post.title} - ${post.url}`); } ``` @@ -53,31 +54,31 @@ for (const post of result.parsedOutput.posts) { ```ts const task = await browseruse.tasks.create({ - task: 'Search for the top 10 Hacker News posts and return the title and url.', - schema: TaskOutput, + task: "Search for the top 10 Hacker News posts and return the title and url.", + schema: TaskOutput, }); const stream = browseruse.tasks.stream({ - taskId: task.id, - schema: TaskOutput, + taskId: task.id, + schema: TaskOutput, }); for await (const msg of stream) { - switch (msg.status) { - case 'started': - case 'paused': - case 'stopped': - console.log(`running: ${msg}`); - break; - - case 'finished': - console.log(`done:`); - - for (const post of msg.parsedOutput.posts) { - console.log(`${post.title} - ${post.url}`); - } - break; - } + switch (msg.status) { + case "started": + case "paused": + case "stopped": + console.log(`running: ${msg}`); + break; + + case "finished": + console.log(`done:`); + + for (const post of msg.parsedOutput.posts) { + console.log(`${post.title} - ${post.url}`); + } + break; + } } ``` @@ -86,38 +87,35 @@ for await (const msg of stream) { > We encourage you to use the SDK functions that verify and parse webhook events. ```ts -import { - verifyWebhookEventSignature, - type WebhookAgentTaskStatusUpdatePayload, -} from 'browser-use-sdk/lib/webhooks'; +import { verifyWebhookEventSignature, type WebhookAgentTaskStatusUpdatePayload } from "browser-use-sdk/lib/webhooks"; export async function POST(req: Request) { - const signature = req.headers['x-browser-use-signature'] as string; - const timestamp = req.headers['x-browser-use-timestamp'] as string; - - const event = await verifyWebhookEventSignature( - { - body, - signature, - timestamp, - }, - { - secret: SECRET_KEY, - }, - ); - - if (!event.ok) { - return; - } - - switch (event.event.type) { - case 'agent.task.status_update': - break; - case 'test': - break; - default: - break; - } + const signature = req.headers["x-browser-use-signature"] as string; + const timestamp = req.headers["x-browser-use-timestamp"] as string; + + const event = await verifyWebhookEventSignature( + { + body, + signature, + timestamp, + }, + { + secret: SECRET_KEY, + }, + ); + + if (!event.ok) { + return; + } + + switch (event.event.type) { + case "agent.task.status_update": + break; + case "test": + break; + default: + break; + } } ``` @@ -238,10 +236,10 @@ The log level can be configured in two ways: 2. Using the `logLevel` client option (overrides the environment variable if set) ```ts -import BrowserUse from 'browser-use-sdk'; +import BrowserUse from "browser-use-sdk"; const client = new BrowserUse({ - logLevel: 'debug', // Show all log messages + logLevel: "debug", // Show all log messages }); ``` @@ -266,14 +264,14 @@ When providing a custom logger, the `logLevel` option still controls which messa below the configured level will not be sent to your logger. ```ts -import BrowserUse from 'browser-use-sdk'; -import pino from 'pino'; +import BrowserUse from "browser-use-sdk"; +import pino from "pino"; const logger = pino(); const client = new BrowserUse({ - logger: logger.child({ name: 'BrowserUse' }), - logLevel: 'debug', // Send all messages to pino, allowing it to filter + logger: logger.child({ name: "BrowserUse" }), + logLevel: "debug", // Send all messages to pino, allowing it to filter }); ``` @@ -284,7 +282,7 @@ By default, this library expects a global `fetch` function is defined. If you want to use a different `fetch` function, you can either polyfill the global: ```ts -import fetch from 'my-fetch'; +import fetch from "my-fetch"; globalThis.fetch = fetch; ``` @@ -292,8 +290,8 @@ globalThis.fetch = fetch; Or pass it to the client: ```ts -import BrowserUse from 'browser-use-sdk'; -import fetch from 'my-fetch'; +import BrowserUse from "browser-use-sdk"; +import fetch from "my-fetch"; const client = new BrowserUse({ fetch }); ``` @@ -303,12 +301,12 @@ const client = new BrowserUse({ fetch }); If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.) ```ts -import BrowserUse from 'browser-use-sdk'; +import BrowserUse from "browser-use-sdk"; const client = new BrowserUse({ - fetchOptions: { - // `RequestInit` options - }, + fetchOptions: { + // `RequestInit` options + }, }); ``` @@ -320,39 +318,39 @@ options to requests: **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] ```ts -import BrowserUse from 'browser-use-sdk'; -import * as undici from 'undici'; +import BrowserUse from "browser-use-sdk"; +import * as undici from "undici"; -const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); +const proxyAgent = new undici.ProxyAgent("http://localhost:8888"); const client = new BrowserUse({ - fetchOptions: { - dispatcher: proxyAgent, - }, + fetchOptions: { + dispatcher: proxyAgent, + }, }); ``` **Bun** [[docs](https://bun.sh/guides/http/proxy)] ```ts -import BrowserUse from 'browser-use-sdk'; +import BrowserUse from "browser-use-sdk"; const client = new BrowserUse({ - fetchOptions: { - proxy: 'http://localhost:8888', - }, + fetchOptions: { + proxy: "http://localhost:8888", + }, }); ``` **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] ```ts -import BrowserUse from 'npm:browser-use-sdk'; +import BrowserUse from "npm:browser-use-sdk"; -const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); +const httpClient = Deno.createHttpClient({ proxy: { url: "http://localhost:8888" } }); const client = new BrowserUse({ - fetchOptions: { - client: httpClient, - }, + fetchOptions: { + client: httpClient, + }, }); ``` @@ -379,4 +377,171 @@ If you are interested in other runtime environments, please open or upvote an is ## Contributing -See [the contributing documentation](./CONTRIBUTING.md). +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! + +## Installation + +```sh +npm i -s +``` + +## Reference + +A full reference for this library is available [here](https://github.com/browser-use/browser-use-node/blob/HEAD/./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { BrowserUseClient } from ""; + +const client = new BrowserUseClient({ environment: "YOUR_BASE_URL", apiKey: "YOUR_API_KEY" }); +await client.tasks.createTask({ + task: "task", +}); +``` + +## Request And Response Types + +The SDK exports all request and response types as TypeScript interfaces. Simply import them with the +following namespace: + +```typescript +import { BrowserUse } from "BrowserUse"; + +const request: BrowserUse.ListTasksTasksGetRequest = { + ... +}; +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { BrowserUseError } from "BrowserUse"; + +try { + await client.tasks.createTask(...); +} catch (err) { + if (err instanceof BrowserUseError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + console.log(err.rawResponse); + } +} +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `headers` request option. + +```typescript +const response = await client.tasks.createTask(..., { + headers: { + 'X-Custom-Header': 'custom value' + } +}); +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. + +```typescript +const response = await client.tasks.createTask(..., { + queryParams: { + 'customQueryParamKey': 'custom query param value' + } +}); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.tasks.createTask(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.tasks.createTask(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.tasks.createTask(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. +The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. + +```typescript +const { data, rawResponse } = await client.tasks.createTask(...).withRawResponse(); + +console.log(data); +console.log(rawResponse.headers['X-My-Header']); +``` + +### Runtime Compatibility + +The SDK works in the following runtimes: + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + +### Customizing Fetch Client + +The SDK provides a way for you to customize the underlying HTTP client / Fetch function. If you're running in an +unsupported environment, this provides a way for you to break glass and ensure the SDK works. + +```typescript +import { BrowserUseClient } from "BrowserUse"; + +const client = new BrowserUseClient({ + ... + fetcher: // provide your implementation here +}); +``` diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index fa6b52c..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Security Policy - -## Reporting Security Issues - -This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. - -To report a security issue, please contact the Stainless team at security@stainless.com. - -## Responsible Disclosure - -We appreciate the efforts of security researchers and individuals who help us maintain the security of -SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible -disclosure practices by allowing us a reasonable amount of time to investigate and address the issue -before making any information public. - -## Reporting Non-SDK Related Security Issues - -If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Browser Use, please follow the respective company's security reporting guidelines. - -### Browser Use Terms and Policies - -Please contact support@browser-use.com for any questions or concerns regarding the security of our services. - ---- - -Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/api.md b/api.md deleted file mode 100644 index 14e49d5..0000000 --- a/api.md +++ /dev/null @@ -1,104 +0,0 @@ -# Users - -## Me - -Types: - -- MeRetrieveResponse - -Methods: - -- client.users.me.retrieve() -> MeRetrieveResponse - -### Files - -Types: - -- FileCreatePresignedURLResponse - -Methods: - -- client.users.me.files.createPresignedURL({ ...params }) -> FileCreatePresignedURLResponse - -# Tasks - -Types: - -- FileView -- TaskItemView -- TaskStatus -- TaskStepView -- TaskView -- TaskCreateResponse -- TaskListResponse -- TaskGetLogsResponse -- TaskGetOutputFileResponse -- TaskGetUserUploadedFileResponse - -Methods: - -- client.tasks.create({ ...params }) -> TaskCreateResponse -- client.tasks.retrieve(taskID) -> TaskView -- client.tasks.update(taskID, { ...params }) -> TaskView -- client.tasks.list({ ...params }) -> TaskListResponse -- client.tasks.getLogs(taskID) -> TaskGetLogsResponse -- client.tasks.getOutputFile(fileID, { ...params }) -> TaskGetOutputFileResponse -- client.tasks.getUserUploadedFile(fileID, { ...params }) -> TaskGetUserUploadedFileResponse - -# Sessions - -Types: - -- SessionStatus -- SessionView -- SessionListResponse - -Methods: - -- client.sessions.retrieve(sessionID) -> SessionView -- client.sessions.update(sessionID, { ...params }) -> SessionView -- client.sessions.list({ ...params }) -> SessionListResponse -- client.sessions.delete(sessionID) -> void - -## PublicShare - -Types: - -- ShareView - -Methods: - -- client.sessions.publicShare.create(sessionID) -> ShareView -- client.sessions.publicShare.retrieve(sessionID) -> ShareView -- client.sessions.publicShare.delete(sessionID) -> void - -# BrowserProfiles - -Types: - -- BrowserProfileView -- ProxyCountryCode -- BrowserProfileListResponse - -Methods: - -- client.browserProfiles.create({ ...params }) -> BrowserProfileView -- client.browserProfiles.retrieve(profileID) -> BrowserProfileView -- client.browserProfiles.update(profileID, { ...params }) -> BrowserProfileView -- client.browserProfiles.list({ ...params }) -> BrowserProfileListResponse -- client.browserProfiles.delete(profileID) -> void - -# AgentProfiles - -Types: - -- AgentProfileView -- AgentProfileListResponse - -Methods: - -- client.agentProfiles.create({ ...params }) -> AgentProfileView -- client.agentProfiles.retrieve(profileID) -> AgentProfileView -- client.agentProfiles.update(profileID, { ...params }) -> AgentProfileView -- client.agentProfiles.list({ ...params }) -> AgentProfileListResponse -- client.agentProfiles.delete(profileID) -> void diff --git a/assets/cloud-banner-js.png b/assets/cloud-banner-js.png deleted file mode 100644 index fb9f50c..0000000 Binary files a/assets/cloud-banner-js.png and /dev/null differ diff --git a/bin/check-release-environment b/bin/check-release-environment deleted file mode 100644 index e4b6d58..0000000 --- a/bin/check-release-environment +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -errors=() - -if [ -z "${NPM_TOKEN}" ]; then - errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") -fi - -lenErrors=${#errors[@]} - -if [[ lenErrors -gt 0 ]]; then - echo -e "Found the following errors in the release environment:\n" - - for error in "${errors[@]}"; do - echo -e "- $error\n" - done - - exit 1 -fi - -echo "The environment is ready to push releases!" - diff --git a/bin/publish-npm b/bin/publish-npm deleted file mode 100644 index 45e8aa8..0000000 --- a/bin/publish-npm +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" - -yarn build -cd dist - -# Get package name and version from package.json -PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" -VERSION="$(jq -r -e '.version' ./package.json)" - -# Get latest version from npm -# -# If the package doesn't exist, npm will return: -# { -# "error": { -# "code": "E404", -# "summary": "Unpublished on 2025-06-05T09:54:53.528Z", -# "detail": "'the_package' is not in this registry..." -# } -# } -NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" - -# Check if we got an E404 error -if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then - # Package doesn't exist yet, no last version - LAST_VERSION="" -elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then - # Report other errors - echo "ERROR: npm returned unexpected data:" - echo "$NPM_INFO" - exit 1 -else - # Success - get the version - LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes -fi - -# Check if current version is pre-release (e.g. alpha / beta / rc) -CURRENT_IS_PRERELEASE=false -if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then - CURRENT_IS_PRERELEASE=true - CURRENT_TAG="${BASH_REMATCH[1]}" -fi - -# Check if last version is a stable release -LAST_IS_STABLE_RELEASE=true -if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then - LAST_IS_STABLE_RELEASE=false -fi - -# Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease. -if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then - TAG="$CURRENT_TAG" -else - TAG="latest" -fi - -# Publish with the appropriate tag -yarn publish --tag "$TAG" diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 8158e2f..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,42 +0,0 @@ -// @ts-check -import tseslint from 'typescript-eslint'; -import unusedImports from 'eslint-plugin-unused-imports'; -import prettier from 'eslint-plugin-prettier'; - -export default tseslint.config( - { - languageOptions: { - parser: tseslint.parser, - parserOptions: { sourceType: 'module' }, - }, - files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'], - ignores: ['dist/'], - plugins: { - '@typescript-eslint': tseslint.plugin, - 'unused-imports': unusedImports, - prettier, - }, - rules: { - 'no-unused-vars': 'off', - 'prettier/prettier': 'error', - 'unused-imports/no-unused-imports': 'error', - 'no-restricted-imports': [ - 'error', - { - patterns: [ - { - regex: '^browser-use-sdk(/.*)?', - message: 'Use a relative import, not a package import.', - }, - ], - }, - ], - }, - }, - { - files: ['tests/**', 'examples/**'], - rules: { - 'no-restricted-imports': 'off', - }, - }, -); diff --git a/examples/.env.example b/examples/.env.example deleted file mode 100644 index ec44747..0000000 --- a/examples/.env.example +++ /dev/null @@ -1,22 +0,0 @@ -# ---------------------------------------------------------------------------- -# -# :GUIDE: -# -# Copy `.env.example` to `.env` to run all examples without any -# additional setup. You can also manually export variables -# and run each example separately. -# -# ---------------------------------------------------------------------------- - -# API ------------------------------------------------------------------------ - -# Browser Use API Key -BROWSER_USE_API_KEY="" - -# Webhooks ------------------------------------------------------------------- - -# NOTE: Use something simple in development. In production, Browser Use Cloud -# will give you the production secret. -SECRET_KEY="secret" - -# ---------------------------------------------------------------------------- \ No newline at end of file diff --git a/examples/.keep b/examples/.keep deleted file mode 100644 index 0651c89..0000000 --- a/examples/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store example files demonstrating usage of this SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/examples/retrieve.ts b/examples/retrieve.ts deleted file mode 100755 index 613ab94..0000000 --- a/examples/retrieve.ts +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env -S npm run tsn -T - -import { BrowserUse } from 'browser-use-sdk'; - -import { env, spinner } from './utils'; -import z from 'zod'; - -env(); - -// gets API Key from environment variable BROWSER_USE_API_KEY -const browseruse = new BrowserUse(); - -async function basic() { - let log = 'starting'; - const stop = spinner(() => log); - - // Create Task - const rsp = await browseruse.tasks.create({ - task: "What's the weather line in SF and what's the temperature?", - agentSettings: { llm: 'gemini-2.5-flash' }, - }); - - poll: do { - // Wait for Task to Finish - const status = await browseruse.tasks.retrieve(rsp.id); - - switch (status.status) { - case 'started': - case 'paused': - case 'stopped': - log = `agent ${status.status} - live: ${status.session.liveUrl}`; - - await new Promise((resolve) => setTimeout(resolve, 2000)); - break; - - case 'finished': - stop(); - - console.log(status.doneOutput); - break poll; - } - } while (true); -} - -// Define Structured Output Schema -const HackerNewsResponse = z.object({ - title: z.string(), - url: z.string(), - score: z.number(), -}); - -const TaskOutput = z.object({ - posts: z.array(HackerNewsResponse), -}); - -async function structured() { - let log = 'starting'; - const stop = spinner(() => log); - - // Create Task - const rsp = await browseruse.tasks.create({ - task: 'Extract top 10 Hacker News posts and return the title, url, and score', - schema: TaskOutput, - agentSettings: { llm: 'gpt-4.1' }, - }); - - poll: do { - // Wait for Task to Finish - const status = await browseruse.tasks.retrieve({ - taskId: rsp.id, - schema: TaskOutput, - }); - - switch (status.status) { - case 'started': - case 'paused': - case 'stopped': { - log = `agent ${status.status} ${status.session.liveUrl} | ${status.steps.length} steps`; - - await new Promise((resolve) => setTimeout(resolve, 2000)); - - break; - } - - case 'finished': - if (status.parsedOutput == null) { - throw new Error('No output'); - } - - stop(); - - // Print Structured Output - console.log('Top Hacker News Posts:'); - - for (const post of status.parsedOutput.posts) { - console.log(` - ${post.title} (${post.score}) ${post.url}`); - } - - break poll; - } - } while (true); -} - -basic() - .then(() => structured()) - .catch(console.error); diff --git a/examples/run.ts b/examples/run.ts deleted file mode 100755 index d515892..0000000 --- a/examples/run.ts +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env -S npm run tsn -T - -import { BrowserUse } from 'browser-use-sdk'; -import { z } from 'zod'; - -import { env } from './utils'; - -env(); - -// gets API Key from environment variable BROWSER_USE_API_KEY -const browseruse = new BrowserUse(); - -async function basic() { - console.log(`Basic: Running Task...`); - - // Create Task - const rsp = await browseruse.tasks.run({ - task: "What's the weather line in SF and what's the temperature?", - agentSettings: { llm: 'gemini-2.5-flash' }, - }); - - console.log(`Basic: ${rsp.doneOutput}`); - - console.log(`Basic: DONE`); -} - -const HackerNewsResponse = z.object({ - title: z.string(), - url: z.string(), -}); - -const TaskOutput = z.object({ - posts: z.array(HackerNewsResponse), -}); - -async function structured() { - console.log(`Structured: Running Task...`); - - // Create Task - const rsp = await browseruse.tasks.run({ - task: 'Search for the top 10 Hacker News posts and return the title and url!', - schema: TaskOutput, - agentSettings: { llm: 'gpt-4.1' }, - }); - - const posts = rsp.parsedOutput?.posts; - - if (posts == null) { - throw new Error('Structured: No posts found'); - } - - console.log(`Structured: Top Hacker News posts:`); - - for (const post of posts) { - console.log(` - ${post.title} - ${post.url}`); - } - - console.log(`\nStructured: DONE`); -} - -basic() - .then(() => structured()) - .catch(console.error); diff --git a/examples/stream.ts b/examples/stream.ts deleted file mode 100755 index da9139f..0000000 --- a/examples/stream.ts +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env -S npm run tsn -T - -import { BrowserUse } from 'browser-use-sdk'; - -import { env } from './utils'; -import z from 'zod'; - -env(); - -async function basic() { - // gets API Key from environment variable BROWSER_USE_API_KEY - const browseruse = new BrowserUse(); - - console.log('Basic: Creating task and starting stream...'); - - // Create a task and get the stream - const task = await browseruse.tasks.create({ - task: 'What is the weather in San Francisco?', - agentSettings: { llm: 'gemini-2.5-flash' }, - }); - - const gen = browseruse.tasks.stream(task.id); - - for await (const msg of gen) { - console.log( - `Basic: ${msg.data.status} ${msg.data.session.liveUrl} ${msg.data.steps[msg.data.steps.length - 1]?.nextGoal}`, - ); - - if (msg.data.status === 'finished') { - console.log(`Basic: ${msg.data.doneOutput}`); - } - } - - console.log('\nBasic: Stream completed'); -} - -const HackerNewsResponse = z.object({ - title: z.string(), - url: z.string(), - score: z.number(), -}); - -const TaskOutput = z.object({ - posts: z.array(HackerNewsResponse), -}); - -async function structured() { - // gets API Key from environment variable BROWSER_USE_API_KEY - const browseruse = new BrowserUse(); - - console.log('Structured: Creating task and starting stream...\n'); - - // Create a task and get the stream - const task = await browseruse.tasks.create({ - task: 'Extract top 10 Hacker News posts and return the title, url, and score', - schema: TaskOutput, - agentSettings: { llm: 'gpt-4.1' }, - }); - - const stream = browseruse.tasks.stream({ - taskId: task.id, - schema: TaskOutput, - }); - - for await (const msg of stream) { - // Regular - process.stdout.write(`Structured: ${msg.data.status}`); - if (msg.data.session.liveUrl) { - process.stdout.write(` | Live URL: ${msg.data.session.liveUrl}`); - } - - if (msg.data.steps.length > 0) { - const latestStep = msg.data.steps[msg.data.steps.length - 1]; - process.stdout.write(` | ${latestStep!.nextGoal}`); - } - - process.stdout.write('\n'); - - // Output - if (msg.data.status === 'finished') { - process.stdout.write(`\n\nOUTPUT:`); - - for (const post of msg.data.parsedOutput!.posts) { - process.stdout.write(`\n - ${post.title} (${post.score}) ${post.url}`); - } - } - } - - console.log('\nStructured: Stream completed'); -} - -basic() - .then(() => structured()) - .catch(console.error); diff --git a/examples/utils.ts b/examples/utils.ts deleted file mode 100644 index f65c3a1..0000000 --- a/examples/utils.ts +++ /dev/null @@ -1,34 +0,0 @@ -import dotenv from '@dotenvx/dotenvx'; - -const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - -/** - * Start a spinner that updates the text every 100ms. - * - * @param renderText - A function that returns the text to display. - * @returns A function to stop the spinner. - */ -export function spinner(renderText: () => string): () => void { - let frameIndex = 0; - const interval = setInterval(() => { - const frame = SPINNER_FRAMES[frameIndex++ % SPINNER_FRAMES.length]; - const text = `${frame} ${renderText()}`; - if (typeof process.stdout.clearLine === 'function') { - process.stdout.clearLine(0); - process.stdout.cursorTo(0); - } - process.stdout.write(text); - }, 100); - - return () => { - clearInterval(interval); - if (typeof process.stdout.clearLine === 'function') { - process.stdout.clearLine(0); - process.stdout.cursorTo(0); - } - }; -} - -export function env() { - dotenv.config({ path: [__dirname + '/.env', '.env'] }); -} diff --git a/examples/webhook.ts b/examples/webhook.ts deleted file mode 100755 index c76fb77..0000000 --- a/examples/webhook.ts +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env -S npm run tsn -T - -import { BrowserUse } from 'browser-use-sdk'; -import { - verifyWebhookEventSignature, - type WebhookAgentTaskStatusUpdatePayload, -} from 'browser-use-sdk/lib/webhooks'; -import { createServer, IncomingMessage, type Server, type ServerResponse } from 'http'; - -import { env } from './utils'; - -env(); - -const PORT = 3000; -const WAIT_FOR_TASK_FINISH_TIMEOUT = 3 * 60_000; - -// Environment --------------------------------------------------------------- - -const SECRET_KEY = process.env['SECRET_KEY']; - -// API ----------------------------------------------------------------------- - -// gets API Key from environment variable BROWSER_USE_API_KEY -const browseruse = new BrowserUse(); - -// - -const whServerRef: { current: Server | null } = { current: null }; - -async function main() { - if (!SECRET_KEY) { - console.error('SECRET_KEY is not set'); - process.exit(1); - } - - console.log('Starting Browser Use Webhook Example'); - console.log('Run `browser-use listen http://localhost:3000/webhook`!'); - - // Start a Webhook Server - - const callback: { current: ((event: WebhookAgentTaskStatusUpdatePayload) => Promise) | null } = { - current: null, - }; - - const server = createServer(async (req: IncomingMessage, res: ServerResponse) => { - if (req.method === 'POST' && req.url === '/webhook') { - let body = ''; - - req.on('data', (chunk) => { - body += chunk.toString(); - }); - - req.on('end', async () => { - try { - const signature = req.headers['x-browser-use-signature'] as string; - const timestamp = req.headers['x-browser-use-timestamp'] as string; - - const event = await verifyWebhookEventSignature( - { - body, - signature, - timestamp, - }, - { - secret: SECRET_KEY, - }, - ); - - if (!event.ok) { - console.log('❌ Invalid webhook signature'); - console.log(body); - console.log(signature, 'signature'); - console.log(timestamp, 'timestamp'); - console.log(SECRET_KEY, 'SECRET_KEY'); - - res.writeHead(401, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Invalid signature' })); - return; - } - - switch (event.event.type) { - case 'agent.task.status_update': - await callback.current?.(event.event.payload); - - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ received: true })); - break; - case 'test': - console.log('🧪 Test webhook received'); - - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ received: true })); - break; - default: - console.log('🧪 Unknown webhook received'); - - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ received: true })); - break; - } - } catch (error) { - console.error(error); - } - }); - } else if (req.method === 'GET' && req.url === '/health') { - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() })); - } else { - res.writeHead(404, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Not found' })); - } - }); - - whServerRef.current = server; - - server.listen(PORT, () => { - console.log(`🌐 Webhook server listening on port ${PORT}`); - console.log(`🔗 Health check: http://localhost:${PORT}/health`); - }); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // Create Task - console.log('📝 Creating a new task...'); - const task = await browseruse.tasks.create({ - task: "What's the weather like in San Francisco and what's the current temperature?", - }); - - console.log(`🔗 Task created: ${task.id}`); - - await new Promise((resolve, reject) => { - // NOTE: We set a timeout so we can catch it when the task is stuck - // and stop the example. - const interval = setTimeout(() => { - reject(new Error('Task creation timed out')); - }, WAIT_FOR_TASK_FINISH_TIMEOUT); - - // NOTE: We attach the callback to the current reference so we can receive updates from the server. - callback.current = async (payload) => { - if (payload.task_id !== task.id) { - return; - } - - console.log('🔄 Task status updated:', payload.status); - - if (payload.status === 'finished') { - clearTimeout(interval); - resolve(); - } - }; - }).catch((error) => { - console.error(error); - process.exit(1); - }); - - // Fetch final task result - const status = await browseruse.tasks.retrieve(task.id); - - console.log('🎯 Final Task Status'); - console.log('OUTPUT:'); - console.log(status.doneOutput); - - server.close(); -} - -// Handle graceful shutdown -process.on('SIGINT', () => { - console.log('\n👋 Shutting down gracefully...'); - whServerRef.current?.close(); - process.exit(0); -}); - -// - -if (require.main === module) { - main().catch(console.error); -} diff --git a/jest.config.mjs b/jest.config.mjs new file mode 100644 index 0000000..b692700 --- /dev/null +++ b/jest.config.mjs @@ -0,0 +1,42 @@ +/** @type {import('jest').Config} */ +export default { + preset: "ts-jest", + testEnvironment: "node", + projects: [ + { + displayName: "unit", + preset: "ts-jest", + testEnvironment: "node", + moduleNameMapper: { + "^(\.{1,2}/.*)\.js$": "$1", + }, + roots: ["/tests"], + testPathIgnorePatterns: ["\.browser\.(spec|test)\.[jt]sx?$", "/tests/wire/"], + setupFilesAfterEnv: [], + }, + { + displayName: "browser", + preset: "ts-jest", + testEnvironment: "/tests/BrowserTestEnvironment.ts", + moduleNameMapper: { + "^(\.{1,2}/.*)\.js$": "$1", + }, + roots: ["/tests"], + testMatch: ["/tests/unit/**/?(*.)+(browser).(spec|test).[jt]s?(x)"], + setupFilesAfterEnv: [], + }, + , + { + displayName: "wire", + preset: "ts-jest", + testEnvironment: "node", + moduleNameMapper: { + "^(\.{1,2}/.*)\.js$": "$1", + }, + roots: ["/tests/wire"], + setupFilesAfterEnv: ["/tests/mock-server/setup.ts"], + }, + ], + workerThreads: false, + passWithNoTests: true, +}; diff --git a/jest.config.ts b/jest.config.ts deleted file mode 100644 index feaa664..0000000 --- a/jest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { JestConfigWithTsJest } from 'ts-jest'; - -const config: JestConfigWithTsJest = { - preset: 'ts-jest/presets/default-esm', - testEnvironment: 'node', - transform: { - '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], - }, - moduleNameMapper: { - '^browser-use-sdk$': '/src/index.ts', - '^browser-use-sdk/(.*)$': '/src/$1', - }, - modulePathIgnorePatterns: [ - '/ecosystem-tests/', - '/dist/', - '/deno/', - '/deno_tests/', - '/packages/', - ], - testPathIgnorePatterns: ['scripts'], -}; - -export default config; diff --git a/package.json b/package.json index ee4d086..d3de7b0 100644 --- a/package.json +++ b/package.json @@ -1,86 +1,85 @@ { - "name": "browser-use-sdk", - "version": "1.2.0", - "description": "The official TypeScript library for the Browser Use API", - "author": "Browser Use ", - "types": "dist/index.d.ts", - "main": "dist/index.js", - "type": "commonjs", - "repository": "github:browser-use/browser-use-node", - "license": "Apache-2.0", - "packageManager": "yarn@1.22.22", - "files": [ - "**/*" - ], - "private": false, - "publishConfig": { - "access": "public" - }, - "scripts": { - "test": "./scripts/test", - "build": "./scripts/build", - "postbuild": "chmod +x ./dist/lib/bin/cli.js", - "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", - "format": "./scripts/format", - "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", - "tsn": "ts-node -r tsconfig-paths/register", - "cli": "ts-node -r tsconfig-paths/register --cwd $PWD ./src/lib/bin/cli.ts", - "lint": "./scripts/lint", - "fix": "./scripts/format" - }, - "bin": { - "browser-use": "./dist/lib/bin/cli.js" - }, - "dependencies": { - "@dotenvx/dotenvx": "^1.48.4", - "fast-json-stable-stringify": "^2.1.0" - }, - "devDependencies": { - "@arethetypeswrong/cli": "^0.17.0", - "@swc/core": "^1.3.102", - "@swc/jest": "^0.2.29", - "@types/jest": "^29.4.0", - "@types/node": "^24.3.0", - "@types/react": "^19.1.10", - "@typescript-eslint/eslint-plugin": "8.31.1", - "@typescript-eslint/parser": "8.31.1", - "commander": "^14.0.0", - "eslint": "^9.20.1", - "eslint-plugin-prettier": "^5.4.1", - "eslint-plugin-unused-imports": "^4.1.4", - "iconv-lite": "^0.6.3", - "jest": "^29.4.0", - "prettier": "^3.0.0", - "publint": "^0.2.12", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "ts-jest": "^29.1.0", - "ts-node": "^10.5.0", - "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", - "tsconfig-paths": "^4.0.0", - "tslib": "^2.8.1", - "typescript": "5.8.3", - "typescript-eslint": "8.31.1", - "zod": "^4.0.17" - }, - "peerDependencies": { - "react": "^18 || ^19", - "zod": "^4" - }, - "exports": { - ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.js" + "name": "", + "version": "0.0.28", + "private": false, + "repository": "github:browser-use/browser-use-node", + "type": "commonjs", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.mjs", + "types": "./dist/cjs/index.d.ts", + "exports": { + ".": { + "types": "./dist/cjs/index.d.ts", + "import": { + "types": "./dist/esm/index.d.mts", + "default": "./dist/esm/index.mjs" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + }, + "default": "./dist/cjs/index.js" + }, + "./package.json": "./package.json" }, - "./*.mjs": { - "default": "./dist/*.mjs" + "files": [ + "dist", + "reference.md", + "README.md", + "LICENSE" + ], + "scripts": { + "format": "prettier . --write --ignore-unknown", + "build": "yarn build:cjs && yarn build:esm", + "build:cjs": "tsc --project ./tsconfig.cjs.json", + "build:esm": "tsc --project ./tsconfig.esm.json && node scripts/rename-to-esm-files.js dist/esm", + "test": "jest --config jest.config.mjs", + "test:unit": "jest --selectProjects unit", + "test:browser": "jest --selectProjects browser", + "test:wire": "jest --selectProjects wire", + "postbuild": "chmod +x ./dist/lib/bin/cli.js" }, - "./*.js": { - "default": "./dist/*.js" + "dependencies": { + "fast-json-stable-stringify": "^2.1.0" }, - "./*": { - "import": "./dist/*.mjs", - "require": "./dist/*.js" + "peerDependencies": { + "react": "^18 || ^19", + "zod": "^4" + }, + "devDependencies": { + "webpack": "^5.97.1", + "ts-loader": "^9.5.1", + "jest": "^29.7.0", + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.14", + "ts-jest": "^29.3.4", + "jest-environment-jsdom": "^29.7.0", + "msw": "^2.8.4", + "@types/node": "^18.19.70", + "prettier": "^3.4.2", + "typescript": "~5.7.2", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "zod": "^4" + }, + "browser": { + "fs": false, + "os": false, + "path": false, + "stream": false + }, + "packageManager": "yarn@1.22.22", + "engines": { + "node": ">=18.0.0" + }, + "sideEffects": false, + "description": "The official TypeScript library for the Browser Use API", + "author": { + "name": "Browser Use", + "url": "https://browser-use.com", + "email": "support@browser-use.com" + }, + "bin": { + "browser-use": "./dist/lib/bin/cli.js" } - } } diff --git a/reference.md b/reference.md new file mode 100644 index 0000000..2872553 --- /dev/null +++ b/reference.md @@ -0,0 +1,1310 @@ +# Reference + +## Account + +
client.account.getAccountMe() -> BrowserUse.AccountView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get authenticated account information including credit balances and account details. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.account.getAccountMe(); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `Account.RequestOptions` + +
+
+
+
+ +
+
+
+ +## Tasks + +
client.tasks.listTasks({ ...params }) -> BrowserUse.TaskListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get paginated list of AI agent tasks with optional filtering by session and status. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.tasks.listTasks(); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `BrowserUse.ListTasksTasksGetRequest` + +
+
+ +
+
+ +**requestOptions:** `Tasks.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.tasks.createTask({ ...params }) -> BrowserUse.TaskCreatedResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +You can either: + +1. Start a new session with a new task (auto creates a new session) +2. Add a follow-up task to an existing session (agent continues in the same browser session) +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.tasks.createTask({ + task: "task", +}); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `BrowserUse.CreateTaskRequest` + +
+
+ +
+
+ +**requestOptions:** `Tasks.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.tasks.getTask(taskId) -> BrowserUse.TaskView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get detailed task information including status, progress, steps, and file outputs. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.tasks.getTask("task_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**taskId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Tasks.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.tasks.updateTask(taskId, { ...params }) -> BrowserUse.TaskView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Control task execution with stop, pause, resume, or stop task and session actions. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.tasks.updateTask("task_id", { + action: "stop", +}); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**taskId:** `string` + +
+
+ +
+
+ +**request:** `BrowserUse.UpdateTaskRequest` + +
+
+ +
+
+ +**requestOptions:** `Tasks.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.tasks.getTaskLogs(taskId) -> BrowserUse.TaskLogFileResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get secure download URL for task execution logs with step-by-step details. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.tasks.getTaskLogs("task_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**taskId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Tasks.RequestOptions` + +
+
+
+
+ +
+
+
+ +## Sessions + +
client.sessions.listSessions({ ...params }) -> BrowserUse.SessionListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get paginated list of AI agent sessions with optional status filtering. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.sessions.listSessions(); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `BrowserUse.ListSessionsSessionsGetRequest` + +
+
+ +
+
+ +**requestOptions:** `Sessions.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.sessions.getSession(sessionId) -> BrowserUse.SessionView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get detailed session information including status, URLs, and task details. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.sessions.getSession("session_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sessionId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Sessions.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.sessions.deleteSession(sessionId) -> void +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Permanently delete a session and all associated data. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.sessions.deleteSession("session_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sessionId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Sessions.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.sessions.updateSession(sessionId, { ...params }) -> BrowserUse.SessionView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Stop a session and all its running tasks. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.sessions.updateSession("session_id", {}); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sessionId:** `string` + +
+
+ +
+
+ +**request:** `BrowserUse.UpdateSessionRequest` + +
+
+ +
+
+ +**requestOptions:** `Sessions.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.sessions.getSessionPublicShare(sessionId) -> BrowserUse.ShareView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get public share information including URL and usage statistics. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.sessions.getSessionPublicShare("session_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sessionId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Sessions.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.sessions.createSessionPublicShare(sessionId) -> BrowserUse.ShareView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Create or return existing public share for a session. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.sessions.createSessionPublicShare("session_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sessionId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Sessions.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.sessions.deleteSessionPublicShare(sessionId) -> void +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Remove public share for a session. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.sessions.deleteSessionPublicShare("session_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sessionId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Sessions.RequestOptions` + +
+
+
+
+ +
+
+
+ +## Files + +
client.files.userUploadFilePresignedUrl(sessionId, { ...params }) -> BrowserUse.UploadFilePresignedUrlResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Generate a secure presigned URL for uploading files that AI agents can use during tasks. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.files.userUploadFilePresignedUrl("session_id", { + fileName: "fileName", + contentType: "image/jpg", + sizeBytes: 1, +}); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sessionId:** `string` + +
+
+ +
+
+ +**request:** `BrowserUse.UploadFileRequest` + +
+
+ +
+
+ +**requestOptions:** `Files.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.files.getTaskUserUploadedFilePresignedUrl(taskId, fileId) -> BrowserUse.TaskUploadedFileResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get secure download URL for a user uploaded file used in the task. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.files.getTaskUserUploadedFilePresignedUrl("task_id", "file_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**taskId:** `string` + +
+
+ +
+
+ +**fileId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Files.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.files.getTaskOutputFilePresignedUrl(taskId, fileId) -> BrowserUse.TaskOutputFileResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get secure download URL for an output file generated by the AI agent. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.files.getTaskOutputFilePresignedUrl("task_id", "file_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**taskId:** `string` + +
+
+ +
+
+ +**fileId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Files.RequestOptions` + +
+
+
+
+ +
+
+
+ +## Profiles + +
client.profiles.listProfiles({ ...params }) -> BrowserUse.ProfileListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get paginated list of profiles. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.profiles.listProfiles(); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `BrowserUse.ListProfilesProfilesGetRequest` + +
+
+ +
+
+ +**requestOptions:** `Profiles.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.profiles.createProfile() -> BrowserUse.ProfileView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Profiles allow you to preserve the state of the browser between tasks. + +They are most commonly used to allow users to preserve the log-in state in the agent between tasks. +You'd normally create one profile per user and then use it for all their tasks. + +You can create a new profile by calling this endpoint. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.profiles.createProfile(); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestOptions:** `Profiles.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.profiles.getProfile(profileId) -> BrowserUse.ProfileView +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get profile details. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.profiles.getProfile("profile_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**profileId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Profiles.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.profiles.deleteBrowserProfile(profileId) -> void +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Permanently delete a browser profile and its configuration. + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.profiles.deleteBrowserProfile("profile_id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**profileId:** `string` + +
+
+ +
+
+ +**requestOptions:** `Profiles.RequestOptions` + +
+
+
+
+ +
+
+
diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index 1ebd0bd..0000000 --- a/release-please-config.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "packages": { - ".": {} - }, - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", - "include-v-in-tag": true, - "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": false, - "pull-request-header": "Automated Release PR", - "pull-request-title-pattern": "release: ${version}", - "changelog-sections": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "perf", - "section": "Performance Improvements" - }, - { - "type": "revert", - "section": "Reverts" - }, - { - "type": "chore", - "section": "Chores" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "style", - "section": "Styles" - }, - { - "type": "refactor", - "section": "Refactors" - }, - { - "type": "test", - "section": "Tests", - "hidden": true - }, - { - "type": "build", - "section": "Build System" - }, - { - "type": "ci", - "section": "Continuous Integration", - "hidden": true - } - ], - "release-type": "node", - "extra-files": ["src/version.ts", "README.md"] -} diff --git a/scripts/bootstrap b/scripts/bootstrap deleted file mode 100755 index 062a034..0000000 --- a/scripts/bootstrap +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then - brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle - } -fi - -echo "==> Installing Node dependencies…" - -PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") - -$PACKAGE_MANAGER install "$@" diff --git a/scripts/build b/scripts/build deleted file mode 100755 index 9f9668e..0000000 --- a/scripts/build +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -set -exuo pipefail - -cd "$(dirname "$0")/.." - -node scripts/utils/check-version.cjs - -# Build into dist and will publish the package from there, -# so that src/resources/foo.ts becomes /resources/foo.js -# This way importing from `"browser-use-sdk/resources/foo"` works -# even with `"moduleResolution": "node"` - -rm -rf dist; mkdir dist -# Copy src to dist/src and build from dist/src into dist, so that -# the source map for index.js.map will refer to ./src/index.ts etc -cp -rp src README.md dist -for file in LICENSE CHANGELOG.md; do - if [ -e "${file}" ]; then cp "${file}" dist; fi -done -if [ -e "bin/cli" ]; then - mkdir -p dist/bin - cp -p "bin/cli" dist/bin/; -fi -if [ -e "bin/migration-config.json" ]; then - mkdir -p dist/bin - cp -p "bin/migration-config.json" dist/bin/; -fi -# this converts the export map paths for the dist directory -# and does a few other minor things -node scripts/utils/make-dist-package-json.cjs > dist/package.json - -# build to .js/.mjs/.d.ts files -./node_modules/.bin/tsc-multi -# we need to patch index.js so that `new module.exports()` works for cjs backwards -# compat. No way to get that from index.ts because it would cause compile errors -# when building .mjs -node scripts/utils/fix-index-exports.cjs -cp tsconfig.dist-src.json dist/src/tsconfig.json - -node scripts/utils/postprocess-files.cjs - -# make sure that nothing crashes when we require the output CJS or -# import the output ESM -(cd dist && node -e 'require("browser-use-sdk")') -(cd dist && node -e 'import("browser-use-sdk")' --input-type=module) - -if [ -e ./scripts/build-deno ] -then - ./scripts/build-deno -fi diff --git a/scripts/format b/scripts/format deleted file mode 100755 index 7a75640..0000000 --- a/scripts/format +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -echo "==> Running eslint --fix" -./node_modules/.bin/eslint --fix . - -echo "==> Running prettier --write" -# format things eslint didn't -./node_modules/.bin/prettier --write --cache --cache-strategy metadata . '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' diff --git a/scripts/lint b/scripts/lint deleted file mode 100755 index 3ffb78a..0000000 --- a/scripts/lint +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -echo "==> Running eslint" -./node_modules/.bin/eslint . - -echo "==> Building" -./scripts/build - -echo "==> Checking types" -./node_modules/typescript/bin/tsc - -echo "==> Running Are The Types Wrong?" -./node_modules/.bin/attw --pack dist -f json >.attw.json || true -node scripts/utils/attw-report.cjs - -echo "==> Running publint" -./node_modules/.bin/publint dist diff --git a/scripts/mock b/scripts/mock deleted file mode 100755 index 0b28f6e..0000000 --- a/scripts/mock +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run prism mock on the given spec -if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - - # Wait for server to come online - echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do - echo -n "." - sleep 0.1 - done - - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - - echo -else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" -fi diff --git a/scripts/rename-to-esm-files.js b/scripts/rename-to-esm-files.js new file mode 100644 index 0000000..dc1df1c --- /dev/null +++ b/scripts/rename-to-esm-files.js @@ -0,0 +1,123 @@ +#!/usr/bin/env node + +const fs = require("fs").promises; +const path = require("path"); + +const extensionMap = { + ".js": ".mjs", + ".d.ts": ".d.mts", +}; +const oldExtensions = Object.keys(extensionMap); + +async function findFiles(rootPath) { + const files = []; + + async function scan(directory) { + const entries = await fs.readdir(directory, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + if (entry.name !== "node_modules" && !entry.name.startsWith(".")) { + await scan(fullPath); + } + } else if (entry.isFile()) { + if (oldExtensions.some((ext) => entry.name.endsWith(ext))) { + files.push(fullPath); + } + } + } + } + + await scan(rootPath); + return files; +} + +async function updateFiles(files) { + const updatedFiles = []; + for (const file of files) { + const updated = await updateFileContents(file); + updatedFiles.push(updated); + } + + console.log(`Updated imports in ${updatedFiles.length} files.`); +} + +async function updateFileContents(file) { + const content = await fs.readFile(file, "utf8"); + + let newContent = content; + // Update each extension type defined in the map + for (const [oldExt, newExt] of Object.entries(extensionMap)) { + // Handle static imports/exports + const staticRegex = new RegExp(`(import|export)(.+from\\s+['"])(\\.\\.?\\/[^'"]+)(\\${oldExt})(['"])`, "g"); + newContent = newContent.replace(staticRegex, `$1$2$3${newExt}$5`); + + // Handle dynamic imports (yield import, await import, regular import()) + const dynamicRegex = new RegExp( + `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, + "g", + ); + newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); + } + + if (content !== newContent) { + await fs.writeFile(file, newContent, "utf8"); + return true; + } + return false; +} + +async function renameFiles(files) { + let counter = 0; + for (const file of files) { + const ext = oldExtensions.find((ext) => file.endsWith(ext)); + const newExt = extensionMap[ext]; + + if (newExt) { + const newPath = file.slice(0, -ext.length) + newExt; + await fs.rename(file, newPath); + counter++; + } + } + + console.log(`Renamed ${counter} files.`); +} + +async function main() { + try { + const targetDir = process.argv[2]; + if (!targetDir) { + console.error("Please provide a target directory"); + process.exit(1); + } + + const targetPath = path.resolve(targetDir); + const targetStats = await fs.stat(targetPath); + + if (!targetStats.isDirectory()) { + console.error("The provided path is not a directory"); + process.exit(1); + } + + console.log(`Scanning directory: ${targetDir}`); + + const files = await findFiles(targetDir); + + if (files.length === 0) { + console.log("No matching files found."); + process.exit(0); + } + + console.log(`Found ${files.length} files.`); + await updateFiles(files); + await renameFiles(files); + console.log("\nDone!"); + } catch (error) { + console.error("An error occurred:", error.message); + process.exit(1); + } +} + +main(); diff --git a/scripts/test b/scripts/test deleted file mode 100755 index 7bce051..0000000 --- a/scripts/test +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if ! is_overriding_api_base_url && ! prism_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo -fi - -echo "==> Running tests" -./node_modules/.bin/jest "$@" diff --git a/scripts/utils/attw-report.cjs b/scripts/utils/attw-report.cjs deleted file mode 100644 index b3477c0..0000000 --- a/scripts/utils/attw-report.cjs +++ /dev/null @@ -1,24 +0,0 @@ -const fs = require('fs'); -const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems) - .flat() - .filter( - (problem) => - !( - // This is intentional, if the user specifies .mjs they get ESM. - ( - (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) || - // This is intentional for backwards compat reasons. - (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) || - // this is intentional, we deliberately attempt to import types that may not exist from parent node_modules - // folders to better support various runtimes without triggering automatic type acquisition. - (problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules')) - ) - ), - ); -fs.unlinkSync('.attw.json'); -if (problems.length) { - process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n'); - process.exitCode = 1; -} else { - process.stdout.write('Types ok!\n'); -} diff --git a/scripts/utils/check-is-in-git-install.sh b/scripts/utils/check-is-in-git-install.sh deleted file mode 100755 index 1354eb4..0000000 --- a/scripts/utils/check-is-in-git-install.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# Check if you happen to call prepare for a repository that's already in node_modules. -[ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || -# The name of the containing directory that 'npm` uses, which looks like -# $HOME/.npm/_cacache/git-cloneXXXXXX -[ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || -# The name of the containing directory that 'yarn` uses, which looks like -# $(yarn cache dir)/.tmp/XXXXX -[ "$(basename "$(dirname "$PWD")")" = '.tmp' ] diff --git a/scripts/utils/check-version.cjs b/scripts/utils/check-version.cjs deleted file mode 100644 index 86c56df..0000000 --- a/scripts/utils/check-version.cjs +++ /dev/null @@ -1,20 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const main = () => { - const pkg = require('../../package.json'); - const version = pkg['version']; - if (!version) throw 'The version property is not set in the package.json file'; - if (typeof version !== 'string') { - throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; - } - - const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); - const contents = fs.readFileSync(versionFile, 'utf8'); - const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); - fs.writeFileSync(versionFile, output); -}; - -if (require.main === module) { - main(); -} diff --git a/scripts/utils/fix-index-exports.cjs b/scripts/utils/fix-index-exports.cjs deleted file mode 100644 index e5e10b3..0000000 --- a/scripts/utils/fix-index-exports.cjs +++ /dev/null @@ -1,17 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const indexJs = - process.env['DIST_PATH'] ? - path.resolve(process.env['DIST_PATH'], 'index.js') - : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); - -let before = fs.readFileSync(indexJs, 'utf8'); -let after = before.replace( - /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m, - `exports = module.exports = function (...args) { - return new exports.default(...args) - } - $1`.replace(/^ /gm, ''), -); -fs.writeFileSync(indexJs, after, 'utf8'); diff --git a/scripts/utils/git-swap.sh b/scripts/utils/git-swap.sh deleted file mode 100755 index 79d1888..0000000 --- a/scripts/utils/git-swap.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail -# the package is published to NPM from ./dist -# we want the final file structure for git installs to match the npm installs, so we - -# delete everything except ./dist and ./node_modules -find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + - -# move everything from ./dist to . -mv dist/* . - -# delete the now-empty ./dist -rmdir dist diff --git a/scripts/utils/make-dist-package-json.cjs b/scripts/utils/make-dist-package-json.cjs deleted file mode 100644 index 7c24f56..0000000 --- a/scripts/utils/make-dist-package-json.cjs +++ /dev/null @@ -1,21 +0,0 @@ -const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json'); - -function processExportMap(m) { - for (const key in m) { - const value = m[key]; - if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './'); - else processExportMap(value); - } -} -processExportMap(pkgJson.exports); - -for (const key of ['types', 'main', 'module']) { - if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); -} - -delete pkgJson.devDependencies; -delete pkgJson.scripts.prepack; -delete pkgJson.scripts.prepublishOnly; -delete pkgJson.scripts.prepare; - -console.log(JSON.stringify(pkgJson, null, 2)); diff --git a/scripts/utils/postprocess-files.cjs b/scripts/utils/postprocess-files.cjs deleted file mode 100644 index deae575..0000000 --- a/scripts/utils/postprocess-files.cjs +++ /dev/null @@ -1,94 +0,0 @@ -// @ts-check -const fs = require('fs'); -const path = require('path'); - -const distDir = - process.env['DIST_PATH'] ? - path.resolve(process.env['DIST_PATH']) - : path.resolve(__dirname, '..', '..', 'dist'); - -async function* walk(dir) { - for await (const d of await fs.promises.opendir(dir)) { - const entry = path.join(dir, d.name); - if (d.isDirectory()) yield* walk(entry); - else if (d.isFile()) yield entry; - } -} - -async function postprocess() { - for await (const file of walk(distDir)) { - if (!/(\.d)?[cm]?ts$/.test(file)) continue; - - const code = await fs.promises.readFile(file, 'utf8'); - - // strip out lib="dom", types="node", and types="react" references; these - // are needed at build time, but would pollute the user's TS environment - const transformed = code.replace( - /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', - ); - - if (transformed !== code) { - console.error(`wrote ${path.relative(process.cwd(), file)}`); - await fs.promises.writeFile(file, transformed, 'utf8'); - } - } - - const newExports = { - '.': { - require: { - types: './index.d.ts', - default: './index.js', - }, - types: './index.d.mts', - default: './index.mjs', - }, - }; - - for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) { - if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') { - const subpath = './' + entry.name; - newExports[subpath + '/*.mjs'] = { - default: subpath + '/*.mjs', - }; - newExports[subpath + '/*.js'] = { - default: subpath + '/*.js', - }; - newExports[subpath + '/*'] = { - import: subpath + '/*.mjs', - require: subpath + '/*.js', - }; - } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) { - const { name, ext } = path.parse(entry.name); - const subpathWithoutExt = './' + name; - const subpath = './' + entry.name; - newExports[subpathWithoutExt] ||= { import: undefined, require: undefined }; - const isModule = ext[1] === 'm'; - if (isModule) { - newExports[subpathWithoutExt].import = subpath; - } else { - newExports[subpathWithoutExt].require = subpath; - } - newExports[subpath] = { - default: subpath, - }; - } - } - await fs.promises.writeFile( - 'dist/package.json', - JSON.stringify( - Object.assign( - /** @type {Record} */ ( - JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8')) - ), - { - exports: newExports, - }, - ), - null, - 2, - ), - ); -} -postprocess(); diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh deleted file mode 100755 index 6f53b0a..0000000 --- a/scripts/utils/upload-artifact.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail - -RESPONSE=$(curl -X POST "$URL" \ - -H "Authorization: Bearer $AUTH" \ - -H "Content-Type: application/json") - -SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') - -if [[ "$SIGNED_URL" == "null" ]]; then - echo -e "\033[31mFailed to get signed URL.\033[0m" - exit 1 -fi - -UPLOAD_RESPONSE=$(tar -cz dist | curl -v -X PUT \ - -H "Content-Type: application/gzip" \ - --data-binary @- "$SIGNED_URL" 2>&1) - -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: npm install 'https://pkg.stainless.com/s/browser-use-typescript/$SHA'\033[0m" -else - echo -e "\033[31mFailed to upload artifact.\033[0m" - exit 1 -fi diff --git a/src/Client.ts b/src/Client.ts new file mode 100644 index 0000000..eb987bf --- /dev/null +++ b/src/Client.ts @@ -0,0 +1,80 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "./core/index.js"; +import { mergeHeaders } from "./core/headers.js"; +import { Account } from "./api/resources/account/client/Client.js"; +import { Tasks } from "./api/resources/tasks/client/Client.js"; +import { Sessions } from "./api/resources/sessions/client/Client.js"; +import { Files } from "./api/resources/files/client/Client.js"; +import { Profiles } from "./api/resources/profiles/client/Client.js"; + +export declare namespace BrowserUseClient { + export interface Options { + environment: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + apiKey: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | undefined>; + } + + export interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | undefined>; + } +} + +export class BrowserUseClient { + protected readonly _options: BrowserUseClient.Options; + protected _account: Account | undefined; + protected _tasks: Tasks | undefined; + protected _sessions: Sessions | undefined; + protected _files: Files | undefined; + protected _profiles: Profiles | undefined; + + constructor(_options: BrowserUseClient.Options) { + this._options = { + ..._options, + headers: mergeHeaders( + { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "", + "X-Fern-SDK-Version": "0.0.28", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + _options?.headers, + ), + }; + } + + public get account(): Account { + return (this._account ??= new Account(this._options)); + } + + public get tasks(): Tasks { + return (this._tasks ??= new Tasks(this._options)); + } + + public get sessions(): Sessions { + return (this._sessions ??= new Sessions(this._options)); + } + + public get files(): Files { + return (this._files ??= new Files(this._options)); + } + + public get profiles(): Profiles { + return (this._profiles ??= new Profiles(this._options)); + } +} diff --git a/src/api-promise.ts b/src/api-promise.ts deleted file mode 100644 index 8c775ee..0000000 --- a/src/api-promise.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/api-promise instead */ -export * from './core/api-promise'; diff --git a/src/api/errors/BadRequestError.ts b/src/api/errors/BadRequestError.ts new file mode 100644 index 0000000..049e425 --- /dev/null +++ b/src/api/errors/BadRequestError.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as errors from "../../errors/index.js"; +import * as core from "../../core/index.js"; + +export class BadRequestError extends errors.BrowserUseError { + constructor(body?: unknown, rawResponse?: core.RawResponse) { + super({ + message: "BadRequestError", + statusCode: 400, + body: body, + rawResponse: rawResponse, + }); + Object.setPrototypeOf(this, BadRequestError.prototype); + } +} diff --git a/src/api/errors/InternalServerError.ts b/src/api/errors/InternalServerError.ts new file mode 100644 index 0000000..ea6040c --- /dev/null +++ b/src/api/errors/InternalServerError.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as errors from "../../errors/index.js"; +import * as core from "../../core/index.js"; + +export class InternalServerError extends errors.BrowserUseError { + constructor(body?: unknown, rawResponse?: core.RawResponse) { + super({ + message: "InternalServerError", + statusCode: 500, + body: body, + rawResponse: rawResponse, + }); + Object.setPrototypeOf(this, InternalServerError.prototype); + } +} diff --git a/src/api/errors/NotFoundError.ts b/src/api/errors/NotFoundError.ts new file mode 100644 index 0000000..6ad681e --- /dev/null +++ b/src/api/errors/NotFoundError.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as errors from "../../errors/index.js"; +import * as core from "../../core/index.js"; + +export class NotFoundError extends errors.BrowserUseError { + constructor(body?: unknown, rawResponse?: core.RawResponse) { + super({ + message: "NotFoundError", + statusCode: 404, + body: body, + rawResponse: rawResponse, + }); + Object.setPrototypeOf(this, NotFoundError.prototype); + } +} diff --git a/src/api/errors/PaymentRequiredError.ts b/src/api/errors/PaymentRequiredError.ts new file mode 100644 index 0000000..14bac5c --- /dev/null +++ b/src/api/errors/PaymentRequiredError.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as errors from "../../errors/index.js"; +import * as BrowserUse from "../index.js"; +import * as core from "../../core/index.js"; + +export class PaymentRequiredError extends errors.BrowserUseError { + constructor(body: BrowserUse.InsufficientCreditsError, rawResponse?: core.RawResponse) { + super({ + message: "PaymentRequiredError", + statusCode: 402, + body: body, + rawResponse: rawResponse, + }); + Object.setPrototypeOf(this, PaymentRequiredError.prototype); + } +} diff --git a/src/api/errors/UnprocessableEntityError.ts b/src/api/errors/UnprocessableEntityError.ts new file mode 100644 index 0000000..c07b892 --- /dev/null +++ b/src/api/errors/UnprocessableEntityError.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as errors from "../../errors/index.js"; +import * as core from "../../core/index.js"; + +export class UnprocessableEntityError extends errors.BrowserUseError { + constructor(body?: unknown, rawResponse?: core.RawResponse) { + super({ + message: "UnprocessableEntityError", + statusCode: 422, + body: body, + rawResponse: rawResponse, + }); + Object.setPrototypeOf(this, UnprocessableEntityError.prototype); + } +} diff --git a/src/api/errors/index.ts b/src/api/errors/index.ts new file mode 100644 index 0000000..8605852 --- /dev/null +++ b/src/api/errors/index.ts @@ -0,0 +1,5 @@ +export * from "./NotFoundError.js"; +export * from "./UnprocessableEntityError.js"; +export * from "./BadRequestError.js"; +export * from "./PaymentRequiredError.js"; +export * from "./InternalServerError.js"; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..05b354b --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,3 @@ +export * from "./types/index.js"; +export * from "./errors/index.js"; +export * from "./resources/index.js"; diff --git a/src/api/resources/account/client/Client.ts b/src/api/resources/account/client/Client.ts new file mode 100644 index 0000000..46541cd --- /dev/null +++ b/src/api/resources/account/client/Client.ts @@ -0,0 +1,114 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core/index.js"; +import * as BrowserUse from "../../../index.js"; +import { mergeHeaders, mergeOnlyDefinedHeaders } from "../../../../core/headers.js"; +import * as errors from "../../../../errors/index.js"; + +export declare namespace Account { + export interface Options { + environment: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + apiKey: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | undefined>; + } + + export interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | undefined>; + } +} + +export class Account { + protected readonly _options: Account.Options; + + constructor(_options: Account.Options) { + this._options = _options; + } + + /** + * Get authenticated account information including credit balances and account details. + * + * @param {Account.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * + * @example + * await client.account.getAccountMe() + */ + public getAccountMe(requestOptions?: Account.RequestOptions): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getAccountMe(requestOptions)); + } + + private async __getAccountMe( + requestOptions?: Account.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "account/me", + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.AccountView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /account/me."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + protected async _getCustomAuthorizationHeaders() { + const apiKeyValue = await core.Supplier.get(this._options.apiKey); + return { "X-Browser-Use-API-Key": apiKeyValue }; + } +} diff --git a/src/api/resources/account/client/index.ts b/src/api/resources/account/client/index.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/api/resources/account/client/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/src/api/resources/account/index.ts b/src/api/resources/account/index.ts new file mode 100644 index 0000000..914b8c3 --- /dev/null +++ b/src/api/resources/account/index.ts @@ -0,0 +1 @@ +export * from "./client/index.js"; diff --git a/src/api/resources/files/client/Client.ts b/src/api/resources/files/client/Client.ts new file mode 100644 index 0000000..685dfc1 --- /dev/null +++ b/src/api/resources/files/client/Client.ts @@ -0,0 +1,325 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core/index.js"; +import * as BrowserUse from "../../../index.js"; +import { mergeHeaders, mergeOnlyDefinedHeaders } from "../../../../core/headers.js"; +import * as errors from "../../../../errors/index.js"; + +export declare namespace Files { + export interface Options { + environment: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + apiKey: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | undefined>; + } + + export interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | undefined>; + } +} + +export class Files { + protected readonly _options: Files.Options; + + constructor(_options: Files.Options) { + this._options = _options; + } + + /** + * Generate a secure presigned URL for uploading files that AI agents can use during tasks. + * + * @param {string} sessionId + * @param {BrowserUse.UploadFileRequest} request + * @param {Files.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.BadRequestError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * @throws {@link BrowserUse.InternalServerError} + * + * @example + * await client.files.userUploadFilePresignedUrl("session_id", { + * fileName: "fileName", + * contentType: "image/jpg", + * sizeBytes: 1 + * }) + */ + public userUploadFilePresignedUrl( + sessionId: string, + request: BrowserUse.UploadFileRequest, + requestOptions?: Files.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise( + this.__userUploadFilePresignedUrl(sessionId, request, requestOptions), + ); + } + + private async __userUploadFilePresignedUrl( + sessionId: string, + request: BrowserUse.UploadFileRequest, + requestOptions?: Files.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `files/sessions/${encodeURIComponent(sessionId)}/presigned-url`, + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { + data: _response.body as BrowserUse.UploadFilePresignedUrlResponse, + rawResponse: _response.rawResponse, + }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 400: + throw new BrowserUse.BadRequestError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + case 500: + throw new BrowserUse.InternalServerError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError( + "Timeout exceeded when calling POST /files/sessions/{session_id}/presigned-url.", + ); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Get secure download URL for a user uploaded file used in the task. + * + * @param {string} taskId + * @param {string} fileId + * @param {Files.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * @throws {@link BrowserUse.InternalServerError} + * + * @example + * await client.files.getTaskUserUploadedFilePresignedUrl("task_id", "file_id") + */ + public getTaskUserUploadedFilePresignedUrl( + taskId: string, + fileId: string, + requestOptions?: Files.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise( + this.__getTaskUserUploadedFilePresignedUrl(taskId, fileId, requestOptions), + ); + } + + private async __getTaskUserUploadedFilePresignedUrl( + taskId: string, + fileId: string, + requestOptions?: Files.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `files/tasks/${encodeURIComponent(taskId)}/uploaded-files/${encodeURIComponent(fileId)}`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.TaskUploadedFileResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + case 500: + throw new BrowserUse.InternalServerError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError( + "Timeout exceeded when calling GET /files/tasks/{task_id}/uploaded-files/{file_id}.", + ); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Get secure download URL for an output file generated by the AI agent. + * + * @param {string} taskId + * @param {string} fileId + * @param {Files.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * @throws {@link BrowserUse.InternalServerError} + * + * @example + * await client.files.getTaskOutputFilePresignedUrl("task_id", "file_id") + */ + public getTaskOutputFilePresignedUrl( + taskId: string, + fileId: string, + requestOptions?: Files.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise( + this.__getTaskOutputFilePresignedUrl(taskId, fileId, requestOptions), + ); + } + + private async __getTaskOutputFilePresignedUrl( + taskId: string, + fileId: string, + requestOptions?: Files.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `files/tasks/${encodeURIComponent(taskId)}/output-files/${encodeURIComponent(fileId)}`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.TaskOutputFileResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + case 500: + throw new BrowserUse.InternalServerError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError( + "Timeout exceeded when calling GET /files/tasks/{task_id}/output-files/{file_id}.", + ); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + protected async _getCustomAuthorizationHeaders() { + const apiKeyValue = await core.Supplier.get(this._options.apiKey); + return { "X-Browser-Use-API-Key": apiKeyValue }; + } +} diff --git a/src/api/resources/files/client/index.ts b/src/api/resources/files/client/index.ts new file mode 100644 index 0000000..82648c6 --- /dev/null +++ b/src/api/resources/files/client/index.ts @@ -0,0 +1,2 @@ +export {}; +export * from "./requests/index.js"; diff --git a/src/api/resources/files/client/requests/UploadFileRequest.ts b/src/api/resources/files/client/requests/UploadFileRequest.ts new file mode 100644 index 0000000..eb1345f --- /dev/null +++ b/src/api/resources/files/client/requests/UploadFileRequest.ts @@ -0,0 +1,53 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * { + * fileName: "fileName", + * contentType: "image/jpg", + * sizeBytes: 1 + * } + */ +export interface UploadFileRequest { + fileName: string; + contentType: UploadFileRequest.ContentType; + sizeBytes: number; +} + +export namespace UploadFileRequest { + export type ContentType = + | "image/jpg" + | "image/jpeg" + | "image/png" + | "image/gif" + | "image/webp" + | "image/svg+xml" + | "application/pdf" + | "application/msword" + | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + | "application/vnd.ms-excel" + | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + | "text/plain" + | "text/csv" + | "text/markdown"; + export const ContentType = { + ImageJpg: "image/jpg", + ImageJpeg: "image/jpeg", + ImagePng: "image/png", + ImageGif: "image/gif", + ImageWebp: "image/webp", + ImageSvgXml: "image/svg+xml", + ApplicationPdf: "application/pdf", + ApplicationMsword: "application/msword", + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocument: + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ApplicationVndMsExcel: "application/vnd.ms-excel", + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheet: + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + TextPlain: "text/plain", + TextCsv: "text/csv", + TextMarkdown: "text/markdown", + } as const; +} diff --git a/src/api/resources/files/client/requests/index.ts b/src/api/resources/files/client/requests/index.ts new file mode 100644 index 0000000..e109786 --- /dev/null +++ b/src/api/resources/files/client/requests/index.ts @@ -0,0 +1 @@ +export { type UploadFileRequest } from "./UploadFileRequest.js"; diff --git a/src/api/resources/files/index.ts b/src/api/resources/files/index.ts new file mode 100644 index 0000000..914b8c3 --- /dev/null +++ b/src/api/resources/files/index.ts @@ -0,0 +1 @@ +export * from "./client/index.js"; diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts new file mode 100644 index 0000000..0840550 --- /dev/null +++ b/src/api/resources/index.ts @@ -0,0 +1,9 @@ +export * as account from "./account/index.js"; +export * as tasks from "./tasks/index.js"; +export * as sessions from "./sessions/index.js"; +export * as files from "./files/index.js"; +export * as profiles from "./profiles/index.js"; +export * from "./tasks/client/requests/index.js"; +export * from "./sessions/client/requests/index.js"; +export * from "./files/client/requests/index.js"; +export * from "./profiles/client/requests/index.js"; diff --git a/src/api/resources/profiles/client/Client.ts b/src/api/resources/profiles/client/Client.ts new file mode 100644 index 0000000..b829a19 --- /dev/null +++ b/src/api/resources/profiles/client/Client.ts @@ -0,0 +1,372 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core/index.js"; +import * as BrowserUse from "../../../index.js"; +import { mergeHeaders, mergeOnlyDefinedHeaders } from "../../../../core/headers.js"; +import * as errors from "../../../../errors/index.js"; + +export declare namespace Profiles { + export interface Options { + environment: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + apiKey: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | undefined>; + } + + export interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | undefined>; + } +} + +export class Profiles { + protected readonly _options: Profiles.Options; + + constructor(_options: Profiles.Options) { + this._options = _options; + } + + /** + * Get paginated list of profiles. + * + * @param {BrowserUse.ListProfilesProfilesGetRequest} request + * @param {Profiles.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.profiles.listProfiles() + */ + public listProfiles( + request: BrowserUse.ListProfilesProfilesGetRequest = {}, + requestOptions?: Profiles.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__listProfiles(request, requestOptions)); + } + + private async __listProfiles( + request: BrowserUse.ListProfilesProfilesGetRequest = {}, + requestOptions?: Profiles.RequestOptions, + ): Promise> { + const { pageSize, pageNumber } = request; + const _queryParams: Record = {}; + if (pageSize != null) { + _queryParams["pageSize"] = pageSize.toString(); + } + + if (pageNumber != null) { + _queryParams["pageNumber"] = pageNumber.toString(); + } + + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "profiles", + ), + method: "GET", + headers: _headers, + queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.ProfileListResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /profiles."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Profiles allow you to preserve the state of the browser between tasks. + * + * They are most commonly used to allow users to preserve the log-in state in the agent between tasks. + * You'd normally create one profile per user and then use it for all their tasks. + * + * You can create a new profile by calling this endpoint. + * + * @param {Profiles.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.PaymentRequiredError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.profiles.createProfile() + */ + public createProfile(requestOptions?: Profiles.RequestOptions): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__createProfile(requestOptions)); + } + + private async __createProfile( + requestOptions?: Profiles.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "profiles", + ), + method: "POST", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.ProfileView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 402: + throw new BrowserUse.PaymentRequiredError( + _response.error.body as BrowserUse.InsufficientCreditsError, + _response.rawResponse, + ); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling POST /profiles."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Get profile details. + * + * @param {string} profileId + * @param {Profiles.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.profiles.getProfile("profile_id") + */ + public getProfile( + profileId: string, + requestOptions?: Profiles.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getProfile(profileId, requestOptions)); + } + + private async __getProfile( + profileId: string, + requestOptions?: Profiles.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `profiles/${encodeURIComponent(profileId)}`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.ProfileView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /profiles/{profile_id}."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Permanently delete a browser profile and its configuration. + * + * @param {string} profileId + * @param {Profiles.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.profiles.deleteBrowserProfile("profile_id") + */ + public deleteBrowserProfile( + profileId: string, + requestOptions?: Profiles.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__deleteBrowserProfile(profileId, requestOptions)); + } + + private async __deleteBrowserProfile( + profileId: string, + requestOptions?: Profiles.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `profiles/${encodeURIComponent(profileId)}`, + ), + method: "DELETE", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: undefined, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling DELETE /profiles/{profile_id}."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + protected async _getCustomAuthorizationHeaders() { + const apiKeyValue = await core.Supplier.get(this._options.apiKey); + return { "X-Browser-Use-API-Key": apiKeyValue }; + } +} diff --git a/src/api/resources/profiles/client/index.ts b/src/api/resources/profiles/client/index.ts new file mode 100644 index 0000000..82648c6 --- /dev/null +++ b/src/api/resources/profiles/client/index.ts @@ -0,0 +1,2 @@ +export {}; +export * from "./requests/index.js"; diff --git a/src/api/resources/profiles/client/requests/ListProfilesProfilesGetRequest.ts b/src/api/resources/profiles/client/requests/ListProfilesProfilesGetRequest.ts new file mode 100644 index 0000000..f6a2ec1 --- /dev/null +++ b/src/api/resources/profiles/client/requests/ListProfilesProfilesGetRequest.ts @@ -0,0 +1,12 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * {} + */ +export interface ListProfilesProfilesGetRequest { + pageSize?: number; + pageNumber?: number; +} diff --git a/src/api/resources/profiles/client/requests/index.ts b/src/api/resources/profiles/client/requests/index.ts new file mode 100644 index 0000000..6e517be --- /dev/null +++ b/src/api/resources/profiles/client/requests/index.ts @@ -0,0 +1 @@ +export { type ListProfilesProfilesGetRequest } from "./ListProfilesProfilesGetRequest.js"; diff --git a/src/api/resources/profiles/index.ts b/src/api/resources/profiles/index.ts new file mode 100644 index 0000000..914b8c3 --- /dev/null +++ b/src/api/resources/profiles/index.ts @@ -0,0 +1 @@ +export * from "./client/index.js"; diff --git a/src/api/resources/sessions/client/Client.ts b/src/api/resources/sessions/client/Client.ts new file mode 100644 index 0000000..20229c1 --- /dev/null +++ b/src/api/resources/sessions/client/Client.ts @@ -0,0 +1,622 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core/index.js"; +import * as BrowserUse from "../../../index.js"; +import { mergeHeaders, mergeOnlyDefinedHeaders } from "../../../../core/headers.js"; +import * as errors from "../../../../errors/index.js"; + +export declare namespace Sessions { + export interface Options { + environment: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + apiKey: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | undefined>; + } + + export interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | undefined>; + } +} + +export class Sessions { + protected readonly _options: Sessions.Options; + + constructor(_options: Sessions.Options) { + this._options = _options; + } + + /** + * Get paginated list of AI agent sessions with optional status filtering. + * + * @param {BrowserUse.ListSessionsSessionsGetRequest} request + * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.sessions.listSessions() + */ + public listSessions( + request: BrowserUse.ListSessionsSessionsGetRequest = {}, + requestOptions?: Sessions.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__listSessions(request, requestOptions)); + } + + private async __listSessions( + request: BrowserUse.ListSessionsSessionsGetRequest = {}, + requestOptions?: Sessions.RequestOptions, + ): Promise> { + const { pageSize, pageNumber, filterBy } = request; + const _queryParams: Record = {}; + if (pageSize != null) { + _queryParams["pageSize"] = pageSize.toString(); + } + + if (pageNumber != null) { + _queryParams["pageNumber"] = pageNumber.toString(); + } + + if (filterBy != null) { + _queryParams["filterBy"] = filterBy; + } + + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "sessions", + ), + method: "GET", + headers: _headers, + queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.SessionListResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /sessions."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Get detailed session information including status, URLs, and task details. + * + * @param {string} sessionId + * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.sessions.getSession("session_id") + */ + public getSession( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getSession(sessionId, requestOptions)); + } + + private async __getSession( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `sessions/${encodeURIComponent(sessionId)}`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.SessionView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /sessions/{session_id}."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Permanently delete a session and all associated data. + * + * @param {string} sessionId + * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.sessions.deleteSession("session_id") + */ + public deleteSession(sessionId: string, requestOptions?: Sessions.RequestOptions): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__deleteSession(sessionId, requestOptions)); + } + + private async __deleteSession( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `sessions/${encodeURIComponent(sessionId)}`, + ), + method: "DELETE", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: undefined, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling DELETE /sessions/{session_id}."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Stop a session and all its running tasks. + * + * @param {string} sessionId + * @param {BrowserUse.UpdateSessionRequest} request + * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.sessions.updateSession("session_id", {}) + */ + public updateSession( + sessionId: string, + request: BrowserUse.UpdateSessionRequest, + requestOptions?: Sessions.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__updateSession(sessionId, request, requestOptions)); + } + + private async __updateSession( + sessionId: string, + request: BrowserUse.UpdateSessionRequest, + requestOptions?: Sessions.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `sessions/${encodeURIComponent(sessionId)}`, + ), + method: "PATCH", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: { ...request, action: "stop" }, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.SessionView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling PATCH /sessions/{session_id}."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Get public share information including URL and usage statistics. + * + * @param {string} sessionId + * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.sessions.getSessionPublicShare("session_id") + */ + public getSessionPublicShare( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getSessionPublicShare(sessionId, requestOptions)); + } + + private async __getSessionPublicShare( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `sessions/${encodeURIComponent(sessionId)}/public-share`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.ShareView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError( + "Timeout exceeded when calling GET /sessions/{session_id}/public-share.", + ); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Create or return existing public share for a session. + * + * @param {string} sessionId + * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.sessions.createSessionPublicShare("session_id") + */ + public createSessionPublicShare( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__createSessionPublicShare(sessionId, requestOptions)); + } + + private async __createSessionPublicShare( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `sessions/${encodeURIComponent(sessionId)}/public-share`, + ), + method: "POST", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.ShareView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError( + "Timeout exceeded when calling POST /sessions/{session_id}/public-share.", + ); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Remove public share for a session. + * + * @param {string} sessionId + * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.sessions.deleteSessionPublicShare("session_id") + */ + public deleteSessionPublicShare( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__deleteSessionPublicShare(sessionId, requestOptions)); + } + + private async __deleteSessionPublicShare( + sessionId: string, + requestOptions?: Sessions.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `sessions/${encodeURIComponent(sessionId)}/public-share`, + ), + method: "DELETE", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: undefined, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError( + "Timeout exceeded when calling DELETE /sessions/{session_id}/public-share.", + ); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + protected async _getCustomAuthorizationHeaders() { + const apiKeyValue = await core.Supplier.get(this._options.apiKey); + return { "X-Browser-Use-API-Key": apiKeyValue }; + } +} diff --git a/src/api/resources/sessions/client/index.ts b/src/api/resources/sessions/client/index.ts new file mode 100644 index 0000000..82648c6 --- /dev/null +++ b/src/api/resources/sessions/client/index.ts @@ -0,0 +1,2 @@ +export {}; +export * from "./requests/index.js"; diff --git a/src/api/resources/sessions/client/requests/ListSessionsSessionsGetRequest.ts b/src/api/resources/sessions/client/requests/ListSessionsSessionsGetRequest.ts new file mode 100644 index 0000000..4f0ef06 --- /dev/null +++ b/src/api/resources/sessions/client/requests/ListSessionsSessionsGetRequest.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../../../../index.js"; + +/** + * @example + * {} + */ +export interface ListSessionsSessionsGetRequest { + pageSize?: number; + pageNumber?: number; + filterBy?: BrowserUse.SessionStatus; +} diff --git a/src/api/resources/sessions/client/requests/UpdateSessionRequest.ts b/src/api/resources/sessions/client/requests/UpdateSessionRequest.ts new file mode 100644 index 0000000..2f6e344 --- /dev/null +++ b/src/api/resources/sessions/client/requests/UpdateSessionRequest.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * {} + */ +export interface UpdateSessionRequest {} diff --git a/src/api/resources/sessions/client/requests/index.ts b/src/api/resources/sessions/client/requests/index.ts new file mode 100644 index 0000000..04a54ed --- /dev/null +++ b/src/api/resources/sessions/client/requests/index.ts @@ -0,0 +1,2 @@ +export { type ListSessionsSessionsGetRequest } from "./ListSessionsSessionsGetRequest.js"; +export { type UpdateSessionRequest } from "./UpdateSessionRequest.js"; diff --git a/src/api/resources/sessions/index.ts b/src/api/resources/sessions/index.ts new file mode 100644 index 0000000..914b8c3 --- /dev/null +++ b/src/api/resources/sessions/index.ts @@ -0,0 +1 @@ +export * from "./client/index.js"; diff --git a/src/api/resources/tasks/client/Client.ts b/src/api/resources/tasks/client/Client.ts new file mode 100644 index 0000000..927c8df --- /dev/null +++ b/src/api/resources/tasks/client/Client.ts @@ -0,0 +1,498 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core/index.js"; +import * as BrowserUse from "../../../index.js"; +import { mergeHeaders, mergeOnlyDefinedHeaders } from "../../../../core/headers.js"; +import * as errors from "../../../../errors/index.js"; + +export declare namespace Tasks { + export interface Options { + environment: core.Supplier; + /** Specify a custom URL to connect the client to. */ + baseUrl?: core.Supplier; + apiKey: core.Supplier; + /** Additional headers to include in requests. */ + headers?: Record | undefined>; + } + + export interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional query string parameters to include in the request. */ + queryParams?: Record; + /** Additional headers to include in the request. */ + headers?: Record | undefined>; + } +} + +export class Tasks { + protected readonly _options: Tasks.Options; + + constructor(_options: Tasks.Options) { + this._options = _options; + } + + /** + * Get paginated list of AI agent tasks with optional filtering by session and status. + * + * @param {BrowserUse.ListTasksTasksGetRequest} request + * @param {Tasks.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.tasks.listTasks() + */ + public listTasks( + request: BrowserUse.ListTasksTasksGetRequest = {}, + requestOptions?: Tasks.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__listTasks(request, requestOptions)); + } + + private async __listTasks( + request: BrowserUse.ListTasksTasksGetRequest = {}, + requestOptions?: Tasks.RequestOptions, + ): Promise> { + const { pageSize, pageNumber, sessionId, filterBy, after, before } = request; + const _queryParams: Record = {}; + if (pageSize != null) { + _queryParams["pageSize"] = pageSize.toString(); + } + + if (pageNumber != null) { + _queryParams["pageNumber"] = pageNumber.toString(); + } + + if (sessionId != null) { + _queryParams["sessionId"] = sessionId; + } + + if (filterBy != null) { + _queryParams["filterBy"] = filterBy; + } + + if (after != null) { + _queryParams["after"] = after; + } + + if (before != null) { + _queryParams["before"] = before; + } + + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "tasks", + ), + method: "GET", + headers: _headers, + queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.TaskListResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /tasks."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * You can either: + * 1. Start a new session with a new task (auto creates a new session) + * 2. Add a follow-up task to an existing session (agent continues in the same browser session) + * + * @param {BrowserUse.CreateTaskRequest} request + * @param {Tasks.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.BadRequestError} + * @throws {@link BrowserUse.PaymentRequiredError} + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * @throws {@link BrowserUse.InternalServerError} + * + * @example + * await client.tasks.createTask({ + * task: "task" + * }) + */ + public createTask( + request: BrowserUse.CreateTaskRequest, + requestOptions?: Tasks.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__createTask(request, requestOptions)); + } + + private async __createTask( + request: BrowserUse.CreateTaskRequest, + requestOptions?: Tasks.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + "tasks", + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.TaskCreatedResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 400: + throw new BrowserUse.BadRequestError(_response.error.body as unknown, _response.rawResponse); + case 402: + throw new BrowserUse.PaymentRequiredError( + _response.error.body as BrowserUse.InsufficientCreditsError, + _response.rawResponse, + ); + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + case 500: + throw new BrowserUse.InternalServerError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling POST /tasks."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Get detailed task information including status, progress, steps, and file outputs. + * + * @param {string} taskId + * @param {Tasks.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.tasks.getTask("task_id") + */ + public getTask( + taskId: string, + requestOptions?: Tasks.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getTask(taskId, requestOptions)); + } + + private async __getTask( + taskId: string, + requestOptions?: Tasks.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `tasks/${encodeURIComponent(taskId)}`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.TaskView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /tasks/{task_id}."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Control task execution with stop, pause, resume, or stop task and session actions. + * + * @param {string} taskId + * @param {BrowserUse.UpdateTaskRequest} request + * @param {Tasks.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * + * @example + * await client.tasks.updateTask("task_id", { + * action: "stop" + * }) + */ + public updateTask( + taskId: string, + request: BrowserUse.UpdateTaskRequest, + requestOptions?: Tasks.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__updateTask(taskId, request, requestOptions)); + } + + private async __updateTask( + taskId: string, + request: BrowserUse.UpdateTaskRequest, + requestOptions?: Tasks.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `tasks/${encodeURIComponent(taskId)}`, + ), + method: "PATCH", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.TaskView, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling PATCH /tasks/{task_id}."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + /** + * Get secure download URL for task execution logs with step-by-step details. + * + * @param {string} taskId + * @param {Tasks.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link BrowserUse.NotFoundError} + * @throws {@link BrowserUse.UnprocessableEntityError} + * @throws {@link BrowserUse.InternalServerError} + * + * @example + * await client.tasks.getTaskLogs("task_id") + */ + public getTaskLogs( + taskId: string, + requestOptions?: Tasks.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__getTaskLogs(taskId, requestOptions)); + } + + private async __getTaskLogs( + taskId: string, + requestOptions?: Tasks.RequestOptions, + ): Promise> { + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ ...(await this._getCustomAuthorizationHeaders()) }), + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `tasks/${encodeURIComponent(taskId)}/logs`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as BrowserUse.TaskLogFileResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 404: + throw new BrowserUse.NotFoundError(_response.error.body as unknown, _response.rawResponse); + case 422: + throw new BrowserUse.UnprocessableEntityError( + _response.error.body as unknown, + _response.rawResponse, + ); + case 500: + throw new BrowserUse.InternalServerError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.BrowserUseError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.BrowserUseTimeoutError("Timeout exceeded when calling GET /tasks/{task_id}/logs."); + case "unknown": + throw new errors.BrowserUseError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + + protected async _getCustomAuthorizationHeaders() { + const apiKeyValue = await core.Supplier.get(this._options.apiKey); + return { "X-Browser-Use-API-Key": apiKeyValue }; + } +} diff --git a/src/api/resources/tasks/client/index.ts b/src/api/resources/tasks/client/index.ts new file mode 100644 index 0000000..82648c6 --- /dev/null +++ b/src/api/resources/tasks/client/index.ts @@ -0,0 +1,2 @@ +export {}; +export * from "./requests/index.js"; diff --git a/src/api/resources/tasks/client/requests/CreateTaskRequest.ts b/src/api/resources/tasks/client/requests/CreateTaskRequest.ts new file mode 100644 index 0000000..9ce32c4 --- /dev/null +++ b/src/api/resources/tasks/client/requests/CreateTaskRequest.ts @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../../../../index.js"; + +/** + * @example + * { + * task: "task" + * } + */ +export interface CreateTaskRequest { + systemPromptExtension?: string; + task: string; + sessionId?: string; + startUrl?: string; + maxAgentSteps?: number; + structuredOutput?: string; + metadata?: Record; + secrets?: Record; + allowedDomains?: string[]; + persistenceProfileId?: string; + agent?: BrowserUse.AgentSettings; + proxy?: BrowserUse.ProxySettings; + browser?: BrowserUse.BrowserSettings; + highlightElements?: boolean; +} diff --git a/src/api/resources/tasks/client/requests/ListTasksTasksGetRequest.ts b/src/api/resources/tasks/client/requests/ListTasksTasksGetRequest.ts new file mode 100644 index 0000000..d6ed378 --- /dev/null +++ b/src/api/resources/tasks/client/requests/ListTasksTasksGetRequest.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../../../../index.js"; + +/** + * @example + * {} + */ +export interface ListTasksTasksGetRequest { + pageSize?: number; + pageNumber?: number; + sessionId?: string; + filterBy?: BrowserUse.TaskStatus; + after?: string; + before?: string; +} diff --git a/src/api/resources/tasks/client/requests/UpdateTaskRequest.ts b/src/api/resources/tasks/client/requests/UpdateTaskRequest.ts new file mode 100644 index 0000000..77e8085 --- /dev/null +++ b/src/api/resources/tasks/client/requests/UpdateTaskRequest.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../../../../index.js"; + +/** + * @example + * { + * action: "stop" + * } + */ +export interface UpdateTaskRequest { + action: BrowserUse.TaskUpdateAction; +} diff --git a/src/api/resources/tasks/client/requests/index.ts b/src/api/resources/tasks/client/requests/index.ts new file mode 100644 index 0000000..86ee80a --- /dev/null +++ b/src/api/resources/tasks/client/requests/index.ts @@ -0,0 +1,3 @@ +export { type ListTasksTasksGetRequest } from "./ListTasksTasksGetRequest.js"; +export { type CreateTaskRequest } from "./CreateTaskRequest.js"; +export { type UpdateTaskRequest } from "./UpdateTaskRequest.js"; diff --git a/src/api/resources/tasks/index.ts b/src/api/resources/tasks/index.ts new file mode 100644 index 0000000..914b8c3 --- /dev/null +++ b/src/api/resources/tasks/index.ts @@ -0,0 +1 @@ +export * from "./client/index.js"; diff --git a/src/api/types/AccountNotFoundError.ts b/src/api/types/AccountNotFoundError.ts new file mode 100644 index 0000000..1816e7b --- /dev/null +++ b/src/api/types/AccountNotFoundError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when a account is not found + */ +export interface AccountNotFoundError { + detail?: string; +} diff --git a/src/api/types/AccountView.ts b/src/api/types/AccountView.ts new file mode 100644 index 0000000..446c108 --- /dev/null +++ b/src/api/types/AccountView.ts @@ -0,0 +1,21 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * View model for account information + * + * Attributes: + * monthly_credits_balance_usd: The monthly credits balance in USD + * additional_credits_balance_usd: The additional credits balance in USD + * email: The email address of the user + * name: The name of the user + * signed_up_at: The date and time the user signed up + */ +export interface AccountView { + monthlyCreditsBalanceUsd: number; + additionalCreditsBalanceUsd: number; + email?: string; + name?: string; + signedUpAt: string; +} diff --git a/src/api/types/AgentSettings.ts b/src/api/types/AgentSettings.ts new file mode 100644 index 0000000..ed66877 --- /dev/null +++ b/src/api/types/AgentSettings.ts @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * Agent settings + * + * Attributes: + * llm: The LLM model to use for the agent + * highlight_elements: Whether to highlight elements during agent interaction with the browser + * max_agent_steps: Maximum number of steps the agent can take before stopping + * + * flash_mode: Whether flash mode is enabled + * thinking: Whether thinking mode is enabled + * vision: Whether vision capabilities are enabled + * custom_system_prompt_extension: Optional custom system prompt for the agent + */ +export interface AgentSettings { + llm?: BrowserUse.SupportedLlMs; + flashMode?: boolean; + thinking?: boolean; + vision?: boolean; +} diff --git a/src/api/types/BadRequestErrorBody.ts b/src/api/types/BadRequestErrorBody.ts new file mode 100644 index 0000000..8c925be --- /dev/null +++ b/src/api/types/BadRequestErrorBody.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +export type BadRequestErrorBody = BrowserUse.SessionStoppedError | BrowserUse.SessionHasRunningTaskError; diff --git a/src/api/types/BrowserSettings.ts b/src/api/types/BrowserSettings.ts new file mode 100644 index 0000000..9602261 --- /dev/null +++ b/src/api/types/BrowserSettings.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Browser viewport and behavior settings + * + * Attributes: + * viewport_width: Browser viewport width in pixels + * viewport_height: Browser viewport height in pixels + * is_mobile: Whether the browser should be in mobile view + * store_cache: Whether to store browser cache + */ +export interface BrowserSettings { + viewportWidth?: number; + viewportHeight?: number; + isMobile?: boolean; + storeCache?: boolean; +} diff --git a/src/api/types/CreditsDeductionError.ts b/src/api/types/CreditsDeductionError.ts new file mode 100644 index 0000000..cb32381 --- /dev/null +++ b/src/api/types/CreditsDeductionError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when credits deduction fails + */ +export interface CreditsDeductionError { + detail?: string; +} diff --git a/src/api/types/DownloadUrlGenerationError.ts b/src/api/types/DownloadUrlGenerationError.ts new file mode 100644 index 0000000..f54f3b7 --- /dev/null +++ b/src/api/types/DownloadUrlGenerationError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when download URL generation fails + */ +export interface DownloadUrlGenerationError { + detail?: string; +} diff --git a/src/api/types/FileView.ts b/src/api/types/FileView.ts new file mode 100644 index 0000000..712f9e0 --- /dev/null +++ b/src/api/types/FileView.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * View model for representing an output file generated by the agent + * + * Attributes: + * id: Unique identifier for the output file + * file_name: Name of the output file + */ +export interface FileView { + id: string; + fileName: string; +} diff --git a/src/api/types/HttpValidationError.ts b/src/api/types/HttpValidationError.ts new file mode 100644 index 0000000..ebcc4d1 --- /dev/null +++ b/src/api/types/HttpValidationError.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +export interface HttpValidationError { + detail?: BrowserUse.ValidationError[]; +} diff --git a/src/api/types/InsufficientCreditsError.ts b/src/api/types/InsufficientCreditsError.ts new file mode 100644 index 0000000..ac300fc --- /dev/null +++ b/src/api/types/InsufficientCreditsError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when user has insufficient credits + */ +export interface InsufficientCreditsError { + detail?: string; +} diff --git a/src/api/types/InternalServerErrorBody.ts b/src/api/types/InternalServerErrorBody.ts new file mode 100644 index 0000000..eeb8f14 --- /dev/null +++ b/src/api/types/InternalServerErrorBody.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response for internal server errors + */ +export interface InternalServerErrorBody { + detail?: string; +} diff --git a/src/api/types/NotFoundErrorBody.ts b/src/api/types/NotFoundErrorBody.ts new file mode 100644 index 0000000..4d832d0 --- /dev/null +++ b/src/api/types/NotFoundErrorBody.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +export type NotFoundErrorBody = BrowserUse.TaskNotFoundError | BrowserUse.OutputFileNotFoundError; diff --git a/src/api/types/OutputFileNotFoundError.ts b/src/api/types/OutputFileNotFoundError.ts new file mode 100644 index 0000000..57f6fcb --- /dev/null +++ b/src/api/types/OutputFileNotFoundError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when an output file is not found + */ +export interface OutputFileNotFoundError { + detail?: string; +} diff --git a/src/api/types/PersistenceProfileNotFoundError.ts b/src/api/types/PersistenceProfileNotFoundError.ts new file mode 100644 index 0000000..1bc713b --- /dev/null +++ b/src/api/types/PersistenceProfileNotFoundError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when a persistence profile is not found + */ +export interface PersistenceProfileNotFoundError { + detail?: string; +} diff --git a/src/api/types/ProfileListResponse.ts b/src/api/types/ProfileListResponse.ts new file mode 100644 index 0000000..f279a76 --- /dev/null +++ b/src/api/types/ProfileListResponse.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * Response model for paginated profile list requests + * + * Attributes: + * items: List of profile views for the current page + */ +export interface ProfileListResponse { + items: BrowserUse.ProfileView[]; + totalItems: number; + pageNumber: number; + pageSize: number; +} diff --git a/src/api/types/ProfileView.ts b/src/api/types/ProfileView.ts new file mode 100644 index 0000000..b8df62e --- /dev/null +++ b/src/api/types/ProfileView.ts @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * View model for representing a profile. A profile lets you preserve the login state between sessions. + * + * We recommend that you create a separate profile for each user of your app. + * + * Attributes: + * id: Unique identifier for the profile + * + * last_used_at: Timestamp when the profile was last used + * + * created_at: Timestamp when the profile was created + * updated_at: Timestamp when the profile was last updated + */ +export interface ProfileView { + id: string; + lastUsedAt?: string; + createdAt: string; + updatedAt: string; +} diff --git a/src/api/types/ProxyCountryCode.ts b/src/api/types/ProxyCountryCode.ts new file mode 100644 index 0000000..3a27916 --- /dev/null +++ b/src/api/types/ProxyCountryCode.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export type ProxyCountryCode = "us" | "uk" | "fr" | "it" | "jp" | "au" | "de" | "fi" | "ca" | "in"; +export const ProxyCountryCode = { + Us: "us", + Uk: "uk", + Fr: "fr", + It: "it", + Jp: "jp", + Au: "au", + De: "de", + Fi: "fi", + Ca: "ca", + In: "in", +} as const; diff --git a/src/api/types/ProxySettings.ts b/src/api/types/ProxySettings.ts new file mode 100644 index 0000000..df8d766 --- /dev/null +++ b/src/api/types/ProxySettings.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * Proxy configuration settings + * + * Attributes: + * enable: Whether proxy is enabled + * proxy_country_code: Country code for proxy location + * ad_blocker: Whether ad blocking is enabled + */ +export interface ProxySettings { + enable?: boolean; + proxyCountryCode?: BrowserUse.ProxyCountryCode; + adBlocker?: boolean; +} diff --git a/src/api/types/SessionHasRunningTaskError.ts b/src/api/types/SessionHasRunningTaskError.ts new file mode 100644 index 0000000..881bf4f --- /dev/null +++ b/src/api/types/SessionHasRunningTaskError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when session already has a running task + */ +export interface SessionHasRunningTaskError { + detail?: string; +} diff --git a/src/api/types/SessionItemView.ts b/src/api/types/SessionItemView.ts new file mode 100644 index 0000000..16710be --- /dev/null +++ b/src/api/types/SessionItemView.ts @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * View model for representing a (browser) session with its associated tasks. + * + * Attributes: + * id: Unique identifier for the session. + * status: Current status of the session (active/stopped). + * live_url: URL where the browser can be viewed live in real-time. + * started_at: Timestamp when the session was created and started. + * finished_at: Timestamp when the session was stopped (None if still active). + */ +export interface SessionItemView { + id: string; + status: BrowserUse.SessionStatus; + liveUrl?: string; + startedAt: string; + finishedAt?: string; +} diff --git a/src/api/types/SessionListResponse.ts b/src/api/types/SessionListResponse.ts new file mode 100644 index 0000000..5add508 --- /dev/null +++ b/src/api/types/SessionListResponse.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * Response model for paginated session list requests + * + * Attributes: + * items: List of session views for the current page + */ +export interface SessionListResponse { + items: BrowserUse.SessionItemView[]; + totalItems: number; + pageNumber: number; + pageSize: number; +} diff --git a/src/api/types/SessionNotFoundError.ts b/src/api/types/SessionNotFoundError.ts new file mode 100644 index 0000000..cb7b0d7 --- /dev/null +++ b/src/api/types/SessionNotFoundError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when a session is not found + */ +export interface SessionNotFoundError { + detail?: string; +} diff --git a/src/api/types/SessionStatus.ts b/src/api/types/SessionStatus.ts new file mode 100644 index 0000000..58e9351 --- /dev/null +++ b/src/api/types/SessionStatus.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Enumeration of possible (browser) session states + * + * Attributes: + * ACTIVE: Session is currently active and running (browser is running) + * STOPPED: Session has been stopped and is no longer active (browser is stopped) + */ +export type SessionStatus = "active" | "stopped"; +export const SessionStatus = { + Active: "active", + Stopped: "stopped", +} as const; diff --git a/src/api/types/SessionStoppedError.ts b/src/api/types/SessionStoppedError.ts new file mode 100644 index 0000000..815189f --- /dev/null +++ b/src/api/types/SessionStoppedError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when trying to use a stopped session + */ +export interface SessionStoppedError { + detail?: string; +} diff --git a/src/api/types/SessionUpdateAction.ts b/src/api/types/SessionUpdateAction.ts new file mode 100644 index 0000000..0e7246e --- /dev/null +++ b/src/api/types/SessionUpdateAction.ts @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Available actions that can be performed on a session + * + * Attributes: + * STOP: Stop the session and all its associated tasks (cannot be undone) + */ +export type SessionUpdateAction = "stop"; diff --git a/src/api/types/SessionView.ts b/src/api/types/SessionView.ts new file mode 100644 index 0000000..f22834d --- /dev/null +++ b/src/api/types/SessionView.ts @@ -0,0 +1,29 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * View model for representing a (browser) session with its associated tasks. + * + * Attributes: + * id: Unique identifier for the session. + * status: Current status of the session (active/stopped). + * live_url: URL where the browser can be viewed live in real-time. + * started_at: Timestamp when the session was created and started. + * finished_at: Timestamp when the session was stopped (None if still active). + * tasks: Optional list of tasks associated with this session. + * record_url: URL to access the recorded session playback. + * public_share_url: Optional URL to access the public share of the session. + */ +export interface SessionView { + id: string; + status: BrowserUse.SessionStatus; + liveUrl?: string; + startedAt: string; + finishedAt?: string; + tasks?: BrowserUse.TaskItemView[]; + recordUrl?: string; + publicShareUrl?: string; +} diff --git a/src/api/types/ShareNotFoundError.ts b/src/api/types/ShareNotFoundError.ts new file mode 100644 index 0000000..f58fb21 --- /dev/null +++ b/src/api/types/ShareNotFoundError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when a public share is not found + */ +export interface ShareNotFoundError { + detail?: string; +} diff --git a/src/api/types/ShareView.ts b/src/api/types/ShareView.ts new file mode 100644 index 0000000..414c09f --- /dev/null +++ b/src/api/types/ShareView.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * View model for representing a public share of a session. + * + * Attributes: + * share_token: Token to access the public share. + * share_url: URL to access the public share. + * view_count: Number of times the public share has been viewed. + * last_viewed_at: Timestamp of the last time the public share was viewed (None if never viewed). + */ +export interface ShareView { + shareToken: string; + shareUrl: string; + viewCount: number; + lastViewedAt?: string; +} diff --git a/src/api/types/SupportedLlMs.ts b/src/api/types/SupportedLlMs.ts new file mode 100644 index 0000000..9bd3289 --- /dev/null +++ b/src/api/types/SupportedLlMs.ts @@ -0,0 +1,29 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export type SupportedLlMs = + | "gpt-4.1" + | "gpt-4.1-mini" + | "o4-mini" + | "o3" + | "gemini-2.5-flash" + | "gemini-2.5-pro" + | "claude-sonnet-4-20250514" + | "gpt-4o" + | "gpt-4o-mini" + | "llama-4-maverick-17b-128e-instruct" + | "claude-3-7-sonnet-20250219"; +export const SupportedLlMs = { + Gpt41: "gpt-4.1", + Gpt41Mini: "gpt-4.1-mini", + O4Mini: "o4-mini", + O3: "o3", + Gemini25Flash: "gemini-2.5-flash", + Gemini25Pro: "gemini-2.5-pro", + ClaudeSonnet420250514: "claude-sonnet-4-20250514", + Gpt4O: "gpt-4o", + Gpt4OMini: "gpt-4o-mini", + Llama4Maverick17B128EInstruct: "llama-4-maverick-17b-128e-instruct", + Claude37Sonnet20250219: "claude-3-7-sonnet-20250219", +} as const; diff --git a/src/api/types/TaskCreatedResponse.ts b/src/api/types/TaskCreatedResponse.ts new file mode 100644 index 0000000..6b96f71 --- /dev/null +++ b/src/api/types/TaskCreatedResponse.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Response model for creating a task + * + * Attributes: + * id: Unique identifier for the created task + * session_id: The ID of the agent session this task belongs to + */ +export interface TaskCreatedResponse { + id: string; + sessionId: string; +} diff --git a/src/api/types/TaskItemView.ts b/src/api/types/TaskItemView.ts new file mode 100644 index 0000000..600c55a --- /dev/null +++ b/src/api/types/TaskItemView.ts @@ -0,0 +1,40 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * View model for representing a task with its execution details + * + * Attributes: + * id: Unique identifier for the task + * session_id: ID of the session this task belongs to + * llm: The LLM model used for this task represented as a string + * task: The task prompt/instruction given to the agent + * status: Current status of the task execution + * started_at: Naive UTC timestamp when the task was started + * finished_at: Naive UTC timestamp when the task completed (None if still running) + * metadata: Optional additional metadata associated with the task set by the user + * is_scheduled: Whether this task was created as a scheduled task + * steps: Optional list of execution steps + * output: Final output/result of the task + * user_uploaded_files: Optional list of files uploaded by user for this task + * output_files: Optional list of files generated as output by this task + * browser_use_version: Version of browser-use used for this task (older tasks may not have this set) + * is_success: Whether the task was successful (self-reported by the agent) + */ +export interface TaskItemView { + id: string; + sessionId: string; + llm: string; + task: string; + status: BrowserUse.TaskStatus; + startedAt: string; + finishedAt?: string; + metadata?: Record; + isScheduled: boolean; + output?: string; + browserUseVersion?: string; + isSuccess?: boolean; +} diff --git a/src/api/types/TaskListResponse.ts b/src/api/types/TaskListResponse.ts new file mode 100644 index 0000000..0df385d --- /dev/null +++ b/src/api/types/TaskListResponse.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * Response model for paginated task list requests + * + * Attributes: + * items: List of task views for the current page + */ +export interface TaskListResponse { + items: BrowserUse.TaskItemView[]; + totalItems: number; + pageNumber: number; + pageSize: number; +} diff --git a/src/api/types/TaskLogFileResponse.ts b/src/api/types/TaskLogFileResponse.ts new file mode 100644 index 0000000..8ace8cd --- /dev/null +++ b/src/api/types/TaskLogFileResponse.ts @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Response model for log file requests + * + * Attributes: + * download_url: URL to download the log file + */ +export interface TaskLogFileResponse { + downloadUrl: string; +} diff --git a/src/api/types/TaskNotFoundError.ts b/src/api/types/TaskNotFoundError.ts new file mode 100644 index 0000000..1688e1d --- /dev/null +++ b/src/api/types/TaskNotFoundError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when a task is not found + */ +export interface TaskNotFoundError { + detail?: string; +} diff --git a/src/api/types/TaskOutputFileResponse.ts b/src/api/types/TaskOutputFileResponse.ts new file mode 100644 index 0000000..3705803 --- /dev/null +++ b/src/api/types/TaskOutputFileResponse.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Response model for output file requests + * + * Attributes: + * id: Unique identifier for the output file + * file_name: Name of the output file + * download_url: URL to download the output file + */ +export interface TaskOutputFileResponse { + id: string; + fileName: string; + downloadUrl: string; +} diff --git a/src/api/types/TaskSessionStatus.ts b/src/api/types/TaskSessionStatus.ts new file mode 100644 index 0000000..3dc6f3c --- /dev/null +++ b/src/api/types/TaskSessionStatus.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Enumeration of possible (browser) session states + * + * Attributes: + * ACTIVE: Session is currently active and running (browser is running) + * STOPPED: Session has been stopped and is no longer active (browser is stopped) + */ +export type TaskSessionStatus = "active" | "stopped"; +export const TaskSessionStatus = { + Active: "active", + Stopped: "stopped", +} as const; diff --git a/src/api/types/TaskSessionView.ts b/src/api/types/TaskSessionView.ts new file mode 100644 index 0000000..a34eee1 --- /dev/null +++ b/src/api/types/TaskSessionView.ts @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * View model for representing a session that a task belongs to + * + * Attributes: + * id: Unique identifier for the session + * status: Current status of the session (active/stopped) + * live_url: URL where the browser can be viewed live in real-time. + * started_at: Timestamp when the session was created and started. + * finished_at: Timestamp when the session was stopped (None if still active). + */ +export interface TaskSessionView { + id: string; + status: BrowserUse.TaskSessionStatus; + liveUrl?: string; + startedAt: string; + finishedAt?: string; +} diff --git a/src/api/types/TaskStatus.ts b/src/api/types/TaskStatus.ts new file mode 100644 index 0000000..16df72e --- /dev/null +++ b/src/api/types/TaskStatus.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Enumeration of possible task execution states + * + * Attributes: + * STARTED: Task has been started and is currently running. + * PAUSED: Task execution has been temporarily paused (can be resumed) + * FINISHED: Task has finished and the agent has completed the task. + * STOPPED: Task execution has been manually stopped (cannot be resumed). + */ +export type TaskStatus = "started" | "paused" | "finished" | "stopped"; +export const TaskStatus = { + Started: "started", + Paused: "paused", + Finished: "finished", + Stopped: "stopped", +} as const; diff --git a/src/api/types/TaskStepView.ts b/src/api/types/TaskStepView.ts new file mode 100644 index 0000000..ede904e --- /dev/null +++ b/src/api/types/TaskStepView.ts @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * View model for representing a single step in a task's execution + * + * Attributes: + * number: Sequential step number within the task + * memory: Agent's memory at this step + * evaluation_previous_goal: Agent's evaluation of the previous goal completion + * next_goal: The goal for the next step + * url: Current URL the browser is on for this step + * screenshot_url: Optional URL to the screenshot taken at this step + * actions: List of stringified json actions performed by the agent in this step + */ +export interface TaskStepView { + number: number; + memory: string; + evaluationPreviousGoal: string; + nextGoal: string; + url: string; + screenshotUrl?: string; + actions: string[]; +} diff --git a/src/api/types/TaskUpdateAction.ts b/src/api/types/TaskUpdateAction.ts new file mode 100644 index 0000000..367230f --- /dev/null +++ b/src/api/types/TaskUpdateAction.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Available actions that can be performed on a task + * + * Attributes: + * STOP: Stop the current task execution + * PAUSE: Pause the current task execution + * RESUME: Resume a paused task execution + * STOP_TASK_AND_SESSION: Stop both the task and its parent session + */ +export type TaskUpdateAction = "stop" | "pause" | "resume" | "stop_task_and_session"; +export const TaskUpdateAction = { + Stop: "stop", + Pause: "pause", + Resume: "resume", + StopTaskAndSession: "stop_task_and_session", +} as const; diff --git a/src/api/types/TaskUploadedFileResponse.ts b/src/api/types/TaskUploadedFileResponse.ts new file mode 100644 index 0000000..49a84f3 --- /dev/null +++ b/src/api/types/TaskUploadedFileResponse.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Response model for user uploaded file requests + * + * Attributes: + * id: Unique identifier for the user uploaded file + * file_name: Name of the user uploaded file + * download_url: URL to download the user uploaded file + */ +export interface TaskUploadedFileResponse { + id: string; + fileName: string; + downloadUrl: string; +} diff --git a/src/api/types/TaskView.ts b/src/api/types/TaskView.ts new file mode 100644 index 0000000..90e6647 --- /dev/null +++ b/src/api/types/TaskView.ts @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as BrowserUse from "../index.js"; + +/** + * View model for representing a task with its execution details + * + * Attributes: + * id: Unique identifier for the task + * session_id: ID of the session this task belongs to + * session: The session this task belongs to + * llm: The LLM model used for this task represented as a string + * task: The task prompt/instruction given to the agent + * status: Current status of the task execution + * started_at: Naive UTC timestamp when the task was started + * finished_at: Naive UTC timestamp when the task completed (None if still running) + * metadata: Optional additional metadata associated with the task set by the user + * is_scheduled: Whether this task was created as a scheduled task + * steps: List of execution steps + * output: Final output/result of the task + * user_uploaded_files: List of files uploaded by user for this task + * output_files: List of files generated as output by this task + * browser_use_version: Version of browser-use used for this task (older tasks may not have this set) + * is_success: Whether the task was successful (self-reported by the agent) + */ +export interface TaskView { + id: string; + sessionId: string; + session: BrowserUse.TaskSessionView; + llm: string; + task: string; + status: BrowserUse.TaskStatus; + startedAt: string; + finishedAt?: string; + metadata?: Record; + isScheduled: boolean; + steps: BrowserUse.TaskStepView[]; + output?: string; + userUploadedFiles: BrowserUse.FileView[]; + outputFiles: BrowserUse.FileView[]; + browserUseVersion?: string; + isSuccess?: boolean; +} diff --git a/src/api/types/UnsupportedContentTypeError.ts b/src/api/types/UnsupportedContentTypeError.ts new file mode 100644 index 0000000..b3debbe --- /dev/null +++ b/src/api/types/UnsupportedContentTypeError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response for unsupported content types + */ +export interface UnsupportedContentTypeError { + detail?: string; +} diff --git a/src/api/types/UploadFilePresignedUrlResponse.ts b/src/api/types/UploadFilePresignedUrlResponse.ts new file mode 100644 index 0000000..59176ae --- /dev/null +++ b/src/api/types/UploadFilePresignedUrlResponse.ts @@ -0,0 +1,21 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Response model for a presigned upload URL + * + * Attributes: + * url: The URL to upload the file to + * method: The HTTP method to use for the upload + * fields: The form fields to include in the upload request + * file_name: The name of the file to upload (should be referenced when user wants to use the file in a task) + * expires_in: The number of seconds until the presigned URL expires + */ +export interface UploadFilePresignedUrlResponse { + url: string; + method: "POST"; + fields: Record; + fileName: string; + expiresIn: number; +} diff --git a/src/api/types/UserUploadedFileNotFoundError.ts b/src/api/types/UserUploadedFileNotFoundError.ts new file mode 100644 index 0000000..eaf9b13 --- /dev/null +++ b/src/api/types/UserUploadedFileNotFoundError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Error response when a file is not found + */ +export interface UserUploadedFileNotFoundError { + detail?: string; +} diff --git a/src/api/types/ValidationError.ts b/src/api/types/ValidationError.ts new file mode 100644 index 0000000..1c398a1 --- /dev/null +++ b/src/api/types/ValidationError.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface ValidationError { + loc: ValidationError.Loc.Item[]; + msg: string; + type: string; +} + +export namespace ValidationError { + export type Loc = Loc.Item[]; + + export namespace Loc { + export type Item = string | number; + } +} diff --git a/src/api/types/index.ts b/src/api/types/index.ts new file mode 100644 index 0000000..ffcd08d --- /dev/null +++ b/src/api/types/index.ts @@ -0,0 +1,46 @@ +export * from "./BadRequestErrorBody.js"; +export * from "./NotFoundErrorBody.js"; +export * from "./AccountNotFoundError.js"; +export * from "./AccountView.js"; +export * from "./AgentSettings.js"; +export * from "./BrowserSettings.js"; +export * from "./CreditsDeductionError.js"; +export * from "./DownloadUrlGenerationError.js"; +export * from "./FileView.js"; +export * from "./HttpValidationError.js"; +export * from "./InsufficientCreditsError.js"; +export * from "./InternalServerErrorBody.js"; +export * from "./OutputFileNotFoundError.js"; +export * from "./PersistenceProfileNotFoundError.js"; +export * from "./ProfileListResponse.js"; +export * from "./ProfileView.js"; +export * from "./ProxyCountryCode.js"; +export * from "./ProxySettings.js"; +export * from "./SessionHasRunningTaskError.js"; +export * from "./SessionItemView.js"; +export * from "./SessionListResponse.js"; +export * from "./SessionNotFoundError.js"; +export * from "./SessionStatus.js"; +export * from "./SessionStoppedError.js"; +export * from "./SessionUpdateAction.js"; +export * from "./SessionView.js"; +export * from "./ShareNotFoundError.js"; +export * from "./ShareView.js"; +export * from "./SupportedLlMs.js"; +export * from "./TaskCreatedResponse.js"; +export * from "./TaskItemView.js"; +export * from "./TaskListResponse.js"; +export * from "./TaskLogFileResponse.js"; +export * from "./TaskNotFoundError.js"; +export * from "./TaskOutputFileResponse.js"; +export * from "./TaskSessionStatus.js"; +export * from "./TaskSessionView.js"; +export * from "./TaskStatus.js"; +export * from "./TaskStepView.js"; +export * from "./TaskUpdateAction.js"; +export * from "./TaskUploadedFileResponse.js"; +export * from "./TaskView.js"; +export * from "./UnsupportedContentTypeError.js"; +export * from "./UploadFilePresignedUrlResponse.js"; +export * from "./UserUploadedFileNotFoundError.js"; +export * from "./ValidationError.js"; diff --git a/src/client.ts b/src/client.ts deleted file mode 100644 index abcf08d..0000000 --- a/src/client.ts +++ /dev/null @@ -1,824 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types'; -import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types'; -import { uuid4 } from './internal/utils/uuid'; -import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; -import { sleep } from './internal/utils/sleep'; -export type { Logger, LogLevel } from './internal/utils/log'; -import { castToError, isAbortError } from './internal/errors'; -import type { APIResponseProps } from './internal/parse'; -import { getPlatformHeaders } from './internal/detect-platform'; -import * as Shims from './internal/shims'; -import * as Opts from './internal/request-options'; -import { VERSION } from './version'; -import * as Errors from './core/error'; -import * as Uploads from './core/uploads'; -import * as API from './resources/index'; -import { APIPromise } from './core/api-promise'; -import { - AgentProfileCreateParams, - AgentProfileListParams, - AgentProfileListResponse, - AgentProfileUpdateParams, - AgentProfileView, - AgentProfiles, -} from './resources/agent-profiles'; -import { - BrowserProfileCreateParams, - BrowserProfileListParams, - BrowserProfileListResponse, - BrowserProfileUpdateParams, - BrowserProfileView, - BrowserProfiles, - ProxyCountryCode, -} from './resources/browser-profiles'; -import { - FileView, - TaskCreateParams, - TaskCreateResponse, - TaskGetLogsResponse, - TaskGetOutputFileParams, - TaskGetOutputFileResponse, - TaskGetUserUploadedFileParams, - TaskGetUserUploadedFileResponse, - TaskItemView, - TaskListParams, - TaskListResponse, - TaskStatus, - TaskStepView, - TaskUpdateParams, - TaskView, - Tasks, -} from './resources/tasks'; -import { - SessionListParams, - SessionListResponse, - SessionStatus, - SessionUpdateParams, - SessionView, - Sessions, -} from './resources/sessions/sessions'; -import { Users } from './resources/users/users'; -import { type Fetch } from './internal/builtin-types'; -import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; -import { FinalRequestOptions, RequestOptions } from './internal/request-options'; -import { readEnv } from './internal/utils/env'; -import { - type LogLevel, - type Logger, - formatRequestDetails, - loggerFor, - parseLogLevel, -} from './internal/utils/log'; -import { isEmptyObj } from './internal/utils/values'; - -export interface ClientOptions { - /** - * Defaults to process.env['BROWSER_USE_API_KEY']. - */ - apiKey?: string | undefined; - - /** - * Override the default base URL for the API, e.g., "https://api.example.com/v2/" - * - * Defaults to process.env['BROWSER_USE_BASE_URL']. - */ - baseURL?: string | null | undefined; - - /** - * The maximum amount of time (in milliseconds) that the client should wait for a response - * from the server before timing out a single request. - * - * Note that request timeouts are retried by default, so in a worst-case scenario you may wait - * much longer than this timeout before the promise succeeds or fails. - * - * @unit milliseconds - */ - timeout?: number | undefined; - /** - * Additional `RequestInit` options to be passed to `fetch` calls. - * Properties will be overridden by per-request `fetchOptions`. - */ - fetchOptions?: MergedRequestInit | undefined; - - /** - * Specify a custom `fetch` function implementation. - * - * If not provided, we expect that `fetch` is defined globally. - */ - fetch?: Fetch | undefined; - - /** - * The maximum number of times that the client will retry a request in case of a - * temporary failure, like a network error or a 5XX error from the server. - * - * @default 2 - */ - maxRetries?: number | undefined; - - /** - * Default headers to include with every request to the API. - * - * These can be removed in individual requests by explicitly setting the - * header to `null` in request options. - */ - defaultHeaders?: HeadersLike | undefined; - - /** - * Default query parameters to include with every request to the API. - * - * These can be removed in individual requests by explicitly setting the - * param to `undefined` in request options. - */ - defaultQuery?: Record | undefined; - - /** - * Set the log level. - * - * Defaults to process.env['BROWSER_USE_LOG'] or 'warn' if it isn't set. - */ - logLevel?: LogLevel | undefined; - - /** - * Set the logger. - * - * Defaults to globalThis.console. - */ - logger?: Logger | undefined; -} - -/** - * API Client for interfacing with the Browser Use API. - */ -export class BrowserUse { - apiKey: string; - - baseURL: string; - maxRetries: number; - timeout: number; - logger: Logger | undefined; - logLevel: LogLevel | undefined; - fetchOptions: MergedRequestInit | undefined; - - private fetch: Fetch; - #encoder: Opts.RequestEncoder; - protected idempotencyHeader?: string; - private _options: ClientOptions; - - /** - * API Client for interfacing with the Browser Use API. - * - * @param {string | undefined} [opts.apiKey=process.env['BROWSER_USE_API_KEY'] ?? undefined] - * @param {string} [opts.baseURL=process.env['BROWSER_USE_BASE_URL'] ?? https://api.browser-use.com/api/v2] - Override the default base URL for the API. - * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. - * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. - * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. - * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. - * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. - * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. - */ - constructor({ - baseURL = readEnv('BROWSER_USE_BASE_URL'), - apiKey = readEnv('BROWSER_USE_API_KEY'), - ...opts - }: ClientOptions = {}) { - if (apiKey === undefined) { - throw new Errors.BrowserUseError( - "The BROWSER_USE_API_KEY environment variable is missing or empty; either provide it, or instantiate the BrowserUse client with an apiKey option, like new BrowserUse({ apiKey: 'My API Key' }).", - ); - } - - const options: ClientOptions = { - apiKey, - ...opts, - baseURL: baseURL || `https://api.browser-use.com/api/v2`, - }; - - this.baseURL = options.baseURL!; - this.timeout = options.timeout ?? BrowserUse.DEFAULT_TIMEOUT /* 1 minute */; - this.logger = options.logger ?? console; - const defaultLogLevel = 'warn'; - // Set default logLevel early so that we can log a warning in parseLogLevel. - this.logLevel = defaultLogLevel; - this.logLevel = - parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? - parseLogLevel(readEnv('BROWSER_USE_LOG'), "process.env['BROWSER_USE_LOG']", this) ?? - defaultLogLevel; - this.fetchOptions = options.fetchOptions; - this.maxRetries = options.maxRetries ?? 2; - this.fetch = options.fetch ?? Shims.getDefaultFetch(); - this.#encoder = Opts.FallbackEncoder; - - this._options = options; - - this.apiKey = apiKey; - } - - /** - * Create a new client instance re-using the same options given to the current client with optional overriding. - */ - withOptions(options: Partial): this { - const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ - ...this._options, - baseURL: this.baseURL, - maxRetries: this.maxRetries, - timeout: this.timeout, - logger: this.logger, - logLevel: this.logLevel, - fetch: this.fetch, - fetchOptions: this.fetchOptions, - apiKey: this.apiKey, - ...options, - }); - return client; - } - - /** - * Check whether the base URL is set to its default. - */ - #baseURLOverridden(): boolean { - return this.baseURL !== 'https://api.browser-use.com/api/v2'; - } - - protected defaultQuery(): Record | undefined { - return this._options.defaultQuery; - } - - protected validateHeaders({ values, nulls }: NullableHeaders) { - return; - } - - protected async authHeaders(opts: FinalRequestOptions): Promise { - return buildHeaders([{ 'X-Browser-Use-API-Key': this.apiKey }]); - } - - /** - * Basic re-implementation of `qs.stringify` for primitive types. - */ - protected stringifyQuery(query: Record): string { - return Object.entries(query) - .filter(([_, value]) => typeof value !== 'undefined') - .map(([key, value]) => { - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - } - if (value === null) { - return `${encodeURIComponent(key)}=`; - } - throw new Errors.BrowserUseError( - `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, - ); - }) - .join('&'); - } - - private getUserAgent(): string { - return `${this.constructor.name}/JS ${VERSION}`; - } - - protected defaultIdempotencyKey(): string { - return `stainless-node-retry-${uuid4()}`; - } - - protected makeStatusError( - status: number, - error: Object, - message: string | undefined, - headers: Headers, - ): Errors.APIError { - return Errors.APIError.generate(status, error, message, headers); - } - - buildURL( - path: string, - query: Record | null | undefined, - defaultBaseURL?: string | undefined, - ): string { - const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; - const url = - isAbsoluteURL(path) ? - new URL(path) - : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); - - const defaultQuery = this.defaultQuery(); - if (!isEmptyObj(defaultQuery)) { - query = { ...defaultQuery, ...query }; - } - - if (typeof query === 'object' && query && !Array.isArray(query)) { - url.search = this.stringifyQuery(query as Record); - } - - return url.toString(); - } - - /** - * Used as a callback for mutating the given `FinalRequestOptions` object. - */ - protected async prepareOptions(options: FinalRequestOptions): Promise {} - - /** - * Used as a callback for mutating the given `RequestInit` object. - * - * This is useful for cases where you want to add certain headers based off of - * the request properties, e.g. `method` or `url`. - */ - protected async prepareRequest( - request: RequestInit, - { url, options }: { url: string; options: FinalRequestOptions }, - ): Promise {} - - get(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('get', path, opts); - } - - post(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('post', path, opts); - } - - patch(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('patch', path, opts); - } - - put(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('put', path, opts); - } - - delete(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('delete', path, opts); - } - - private methodRequest( - method: HTTPMethod, - path: string, - opts?: PromiseOrValue, - ): APIPromise { - return this.request( - Promise.resolve(opts).then((opts) => { - return { method, path, ...opts }; - }), - ); - } - - request( - options: PromiseOrValue, - remainingRetries: number | null = null, - ): APIPromise { - return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); - } - - private async makeRequest( - optionsInput: PromiseOrValue, - retriesRemaining: number | null, - retryOfRequestLogID: string | undefined, - ): Promise { - const options = await optionsInput; - const maxRetries = options.maxRetries ?? this.maxRetries; - if (retriesRemaining == null) { - retriesRemaining = maxRetries; - } - - await this.prepareOptions(options); - - const { req, url, timeout } = await this.buildRequest(options, { - retryCount: maxRetries - retriesRemaining, - }); - - await this.prepareRequest(req, { url, options }); - - /** Not an API request ID, just for correlating local log entries. */ - const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0'); - const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`; - const startTime = Date.now(); - - loggerFor(this).debug( - `[${requestLogID}] sending request`, - formatRequestDetails({ - retryOfRequestLogID, - method: options.method, - url, - options, - headers: req.headers, - }), - ); - - if (options.signal?.aborted) { - throw new Errors.APIUserAbortError(); - } - - const controller = new AbortController(); - const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); - const headersTime = Date.now(); - - if (response instanceof Error) { - const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; - if (options.signal?.aborted) { - throw new Errors.APIUserAbortError(); - } - // detect native connection timeout errors - // deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)" - // undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)" - // others do not provide enough information to distinguish timeouts from other connection errors - const isTimeout = - isAbortError(response) || - /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); - if (retriesRemaining) { - loggerFor(this).info( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`, - ); - loggerFor(this).debug( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, - formatRequestDetails({ - retryOfRequestLogID, - url, - durationMs: headersTime - startTime, - message: response.message, - }), - ); - return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); - } - loggerFor(this).info( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`, - ); - loggerFor(this).debug( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, - formatRequestDetails({ - retryOfRequestLogID, - url, - durationMs: headersTime - startTime, - message: response.message, - }), - ); - if (isTimeout) { - throw new Errors.APIConnectionTimeoutError(); - } - throw new Errors.APIConnectionError({ cause: response }); - } - - const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${ - response.ok ? 'succeeded' : 'failed' - } with status ${response.status} in ${headersTime - startTime}ms`; - - if (!response.ok) { - const shouldRetry = await this.shouldRetry(response); - if (retriesRemaining && shouldRetry) { - const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; - - // We don't need the body of this response. - await Shims.CancelReadableStream(response.body); - loggerFor(this).info(`${responseInfo} - ${retryMessage}`); - loggerFor(this).debug( - `[${requestLogID}] response error (${retryMessage})`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - headers: response.headers, - durationMs: headersTime - startTime, - }), - ); - return this.retryRequest( - options, - retriesRemaining, - retryOfRequestLogID ?? requestLogID, - response.headers, - ); - } - - const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`; - - loggerFor(this).info(`${responseInfo} - ${retryMessage}`); - - const errText = await response.text().catch((err: any) => castToError(err).message); - const errJSON = safeJSON(errText); - const errMessage = errJSON ? undefined : errText; - - loggerFor(this).debug( - `[${requestLogID}] response error (${retryMessage})`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - headers: response.headers, - message: errMessage, - durationMs: Date.now() - startTime, - }), - ); - - const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); - throw err; - } - - loggerFor(this).info(responseInfo); - loggerFor(this).debug( - `[${requestLogID}] response start`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - headers: response.headers, - durationMs: headersTime - startTime, - }), - ); - - return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; - } - - async fetchWithTimeout( - url: RequestInfo, - init: RequestInit | undefined, - ms: number, - controller: AbortController, - ): Promise { - const { signal, method, ...options } = init || {}; - if (signal) signal.addEventListener('abort', () => controller.abort()); - - const timeout = setTimeout(() => controller.abort(), ms); - - const isReadableBody = - ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || - (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); - - const fetchOptions: RequestInit = { - signal: controller.signal as any, - ...(isReadableBody ? { duplex: 'half' } : {}), - method: 'GET', - ...options, - }; - if (method) { - // Custom methods like 'patch' need to be uppercased - // See https://github.com/nodejs/undici/issues/2294 - fetchOptions.method = method.toUpperCase(); - } - - try { - // use undefined this binding; fetch errors if bound to something else in browser/cloudflare - return await this.fetch.call(undefined, url, fetchOptions); - } finally { - clearTimeout(timeout); - } - } - - private async shouldRetry(response: Response): Promise { - // Note this is not a standard header. - const shouldRetryHeader = response.headers.get('x-should-retry'); - - // If the server explicitly says whether or not to retry, obey. - if (shouldRetryHeader === 'true') return true; - if (shouldRetryHeader === 'false') return false; - - // Retry on request timeouts. - if (response.status === 408) return true; - - // Retry on lock timeouts. - if (response.status === 409) return true; - - // Retry on rate limits. - if (response.status === 429) return true; - - // Retry internal errors. - if (response.status >= 500) return true; - - return false; - } - - private async retryRequest( - options: FinalRequestOptions, - retriesRemaining: number, - requestLogID: string, - responseHeaders?: Headers | undefined, - ): Promise { - let timeoutMillis: number | undefined; - - // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. - const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); - if (retryAfterMillisHeader) { - const timeoutMs = parseFloat(retryAfterMillisHeader); - if (!Number.isNaN(timeoutMs)) { - timeoutMillis = timeoutMs; - } - } - - // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - const retryAfterHeader = responseHeaders?.get('retry-after'); - if (retryAfterHeader && !timeoutMillis) { - const timeoutSeconds = parseFloat(retryAfterHeader); - if (!Number.isNaN(timeoutSeconds)) { - timeoutMillis = timeoutSeconds * 1000; - } else { - timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); - } - } - - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says, but otherwise calculate a default - if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { - const maxRetries = options.maxRetries ?? this.maxRetries; - timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); - } - await sleep(timeoutMillis); - - return this.makeRequest(options, retriesRemaining - 1, requestLogID); - } - - private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { - const initialRetryDelay = 0.5; - const maxRetryDelay = 8.0; - - const numRetries = maxRetries - retriesRemaining; - - // Apply exponential backoff, but not more than the max. - const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); - - // Apply some jitter, take up to at most 25 percent of the retry time. - const jitter = 1 - Math.random() * 0.25; - - return sleepSeconds * jitter * 1000; - } - - async buildRequest( - inputOptions: FinalRequestOptions, - { retryCount = 0 }: { retryCount?: number } = {}, - ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { - const options = { ...inputOptions }; - const { method, path, query, defaultBaseURL } = options; - - const url = this.buildURL(path!, query as Record, defaultBaseURL); - if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); - options.timeout = options.timeout ?? this.timeout; - const { bodyHeaders, body } = this.buildBody({ options }); - const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); - - const req: FinalizedRequestInit = { - method, - headers: reqHeaders, - ...(options.signal && { signal: options.signal }), - ...((globalThis as any).ReadableStream && - body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), - ...(body && { body }), - ...((this.fetchOptions as any) ?? {}), - ...((options.fetchOptions as any) ?? {}), - }; - - return { req, url, timeout: options.timeout }; - } - - private async buildHeaders({ - options, - method, - bodyHeaders, - retryCount, - }: { - options: FinalRequestOptions; - method: HTTPMethod; - bodyHeaders: HeadersLike; - retryCount: number; - }): Promise { - let idempotencyHeaders: HeadersLike = {}; - if (this.idempotencyHeader && method !== 'get') { - if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); - idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; - } - - const headers = buildHeaders([ - idempotencyHeaders, - { - Accept: 'application/json', - 'User-Agent': this.getUserAgent(), - 'X-Stainless-Retry-Count': String(retryCount), - ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), - ...getPlatformHeaders(), - }, - await this.authHeaders(options), - this._options.defaultHeaders, - bodyHeaders, - options.headers, - ]); - - this.validateHeaders(headers); - - return headers.values; - } - - private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { - bodyHeaders: HeadersLike; - body: BodyInit | undefined; - } { - if (!body) { - return { bodyHeaders: undefined, body: undefined }; - } - const headers = buildHeaders([rawHeaders]); - if ( - // Pass raw type verbatim - ArrayBuffer.isView(body) || - body instanceof ArrayBuffer || - body instanceof DataView || - (typeof body === 'string' && - // Preserve legacy string encoding behavior for now - headers.values.has('content-type')) || - // `Blob` is superset of `File` - ((globalThis as any).Blob && body instanceof (globalThis as any).Blob) || - // `FormData` -> `multipart/form-data` - body instanceof FormData || - // `URLSearchParams` -> `application/x-www-form-urlencoded` - body instanceof URLSearchParams || - // Send chunked stream (each chunk has own `length`) - ((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) - ) { - return { bodyHeaders: undefined, body: body as BodyInit }; - } else if ( - typeof body === 'object' && - (Symbol.asyncIterator in body || - (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) - ) { - return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; - } else { - return this.#encoder({ body, headers }); - } - } - - static BrowserUse = this; - static DEFAULT_TIMEOUT = 60000; // 1 minute - - static BrowserUseError = Errors.BrowserUseError; - static APIError = Errors.APIError; - static APIConnectionError = Errors.APIConnectionError; - static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; - static APIUserAbortError = Errors.APIUserAbortError; - static NotFoundError = Errors.NotFoundError; - static ConflictError = Errors.ConflictError; - static RateLimitError = Errors.RateLimitError; - static BadRequestError = Errors.BadRequestError; - static AuthenticationError = Errors.AuthenticationError; - static InternalServerError = Errors.InternalServerError; - static PermissionDeniedError = Errors.PermissionDeniedError; - static UnprocessableEntityError = Errors.UnprocessableEntityError; - - static toFile = Uploads.toFile; - - users: API.Users = new API.Users(this); - tasks: API.Tasks = new API.Tasks(this); - sessions: API.Sessions = new API.Sessions(this); - browserProfiles: API.BrowserProfiles = new API.BrowserProfiles(this); - agentProfiles: API.AgentProfiles = new API.AgentProfiles(this); -} - -BrowserUse.Users = Users; -BrowserUse.Tasks = Tasks; -BrowserUse.Sessions = Sessions; -BrowserUse.BrowserProfiles = BrowserProfiles; -BrowserUse.AgentProfiles = AgentProfiles; - -export declare namespace BrowserUse { - export type RequestOptions = Opts.RequestOptions; - - export { Users as Users }; - - export { - Tasks as Tasks, - type FileView as FileView, - type TaskItemView as TaskItemView, - type TaskStatus as TaskStatus, - type TaskStepView as TaskStepView, - type TaskView as TaskView, - type TaskCreateResponse as TaskCreateResponse, - type TaskListResponse as TaskListResponse, - type TaskGetLogsResponse as TaskGetLogsResponse, - type TaskGetOutputFileResponse as TaskGetOutputFileResponse, - type TaskGetUserUploadedFileResponse as TaskGetUserUploadedFileResponse, - type TaskCreateParams as TaskCreateParams, - type TaskUpdateParams as TaskUpdateParams, - type TaskListParams as TaskListParams, - type TaskGetOutputFileParams as TaskGetOutputFileParams, - type TaskGetUserUploadedFileParams as TaskGetUserUploadedFileParams, - }; - - export { - Sessions as Sessions, - type SessionStatus as SessionStatus, - type SessionView as SessionView, - type SessionListResponse as SessionListResponse, - type SessionUpdateParams as SessionUpdateParams, - type SessionListParams as SessionListParams, - }; - - export { - BrowserProfiles as BrowserProfiles, - type BrowserProfileView as BrowserProfileView, - type ProxyCountryCode as ProxyCountryCode, - type BrowserProfileListResponse as BrowserProfileListResponse, - type BrowserProfileCreateParams as BrowserProfileCreateParams, - type BrowserProfileUpdateParams as BrowserProfileUpdateParams, - type BrowserProfileListParams as BrowserProfileListParams, - }; - - export { - AgentProfiles as AgentProfiles, - type AgentProfileView as AgentProfileView, - type AgentProfileListResponse as AgentProfileListResponse, - type AgentProfileCreateParams as AgentProfileCreateParams, - type AgentProfileUpdateParams as AgentProfileUpdateParams, - type AgentProfileListParams as AgentProfileListParams, - }; -} diff --git a/src/core/README.md b/src/core/README.md deleted file mode 100644 index 485fce8..0000000 --- a/src/core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `core` - -This directory holds public modules implementing non-resource-specific SDK functionality. diff --git a/src/core/api-promise.ts b/src/core/api-promise.ts deleted file mode 100644 index 8e41c00..0000000 --- a/src/core/api-promise.ts +++ /dev/null @@ -1,92 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { type BrowserUse } from '../client'; - -import { type PromiseOrValue } from '../internal/types'; -import { APIResponseProps, defaultParseResponse } from '../internal/parse'; - -/** - * A subclass of `Promise` providing additional helper methods - * for interacting with the SDK. - */ -export class APIPromise extends Promise { - private parsedPromise: Promise | undefined; - #client: BrowserUse; - - constructor( - client: BrowserUse, - private responsePromise: Promise, - private parseResponse: ( - client: BrowserUse, - props: APIResponseProps, - ) => PromiseOrValue = defaultParseResponse, - ) { - super((resolve) => { - // this is maybe a bit weird but this has to be a no-op to not implicitly - // parse the response body; instead .then, .catch, .finally are overridden - // to parse the response - resolve(null as any); - }); - this.#client = client; - } - - _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { - return new APIPromise(this.#client, this.responsePromise, async (client, props) => - transform(await this.parseResponse(client, props), props), - ); - } - - /** - * Gets the raw `Response` instance instead of parsing the response - * data. - * - * If you want to parse the response body but still get the `Response` - * instance, you can use {@link withResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` - * to your `tsconfig.json`. - */ - asResponse(): Promise { - return this.responsePromise.then((p) => p.response); - } - - /** - * Gets the parsed response data and the raw `Response` instance. - * - * If you just want to get the raw `Response` instance without parsing it, - * you can use {@link asResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` - * to your `tsconfig.json`. - */ - async withResponse(): Promise<{ data: T; response: Response }> { - const [data, response] = await Promise.all([this.parse(), this.asResponse()]); - return { data, response }; - } - - private parse(): Promise { - if (!this.parsedPromise) { - this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data)); - } - return this.parsedPromise; - } - - override then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, - ): Promise { - return this.parse().then(onfulfilled, onrejected); - } - - override catch( - onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, - ): Promise { - return this.parse().catch(onrejected); - } - - override finally(onfinally?: (() => void) | undefined | null): Promise { - return this.parse().finally(onfinally); - } -} diff --git a/src/core/error.ts b/src/core/error.ts deleted file mode 100644 index db88b4c..0000000 --- a/src/core/error.ts +++ /dev/null @@ -1,130 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { castToError } from '../internal/errors'; - -export class BrowserUseError extends Error {} - -export class APIError< - TStatus extends number | undefined = number | undefined, - THeaders extends Headers | undefined = Headers | undefined, - TError extends Object | undefined = Object | undefined, -> extends BrowserUseError { - /** HTTP status for the response that caused the error */ - readonly status: TStatus; - /** HTTP headers for the response that caused the error */ - readonly headers: THeaders; - /** JSON body of the response that caused the error */ - readonly error: TError; - - constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { - super(`${APIError.makeMessage(status, error, message)}`); - this.status = status; - this.headers = headers; - this.error = error; - } - - private static makeMessage(status: number | undefined, error: any, message: string | undefined) { - const msg = - error?.message ? - typeof error.message === 'string' ? - error.message - : JSON.stringify(error.message) - : error ? JSON.stringify(error) - : message; - - if (status && msg) { - return `${status} ${msg}`; - } - if (status) { - return `${status} status code (no body)`; - } - if (msg) { - return msg; - } - return '(no status code or body)'; - } - - static generate( - status: number | undefined, - errorResponse: Object | undefined, - message: string | undefined, - headers: Headers | undefined, - ): APIError { - if (!status || !headers) { - return new APIConnectionError({ message, cause: castToError(errorResponse) }); - } - - const error = errorResponse as Record; - - if (status === 400) { - return new BadRequestError(status, error, message, headers); - } - - if (status === 401) { - return new AuthenticationError(status, error, message, headers); - } - - if (status === 403) { - return new PermissionDeniedError(status, error, message, headers); - } - - if (status === 404) { - return new NotFoundError(status, error, message, headers); - } - - if (status === 409) { - return new ConflictError(status, error, message, headers); - } - - if (status === 422) { - return new UnprocessableEntityError(status, error, message, headers); - } - - if (status === 429) { - return new RateLimitError(status, error, message, headers); - } - - if (status >= 500) { - return new InternalServerError(status, error, message, headers); - } - - return new APIError(status, error, message, headers); - } -} - -export class APIUserAbortError extends APIError { - constructor({ message }: { message?: string } = {}) { - super(undefined, undefined, message || 'Request was aborted.', undefined); - } -} - -export class APIConnectionError extends APIError { - constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { - super(undefined, undefined, message || 'Connection error.', undefined); - // in some environments the 'cause' property is already declared - // @ts-ignore - if (cause) this.cause = cause; - } -} - -export class APIConnectionTimeoutError extends APIConnectionError { - constructor({ message }: { message?: string } = {}) { - super({ message: message ?? 'Request timed out.' }); - } -} - -export class BadRequestError extends APIError<400, Headers> {} - -export class AuthenticationError extends APIError<401, Headers> {} - -export class PermissionDeniedError extends APIError<403, Headers> {} - -export class NotFoundError extends APIError<404, Headers> {} - -export class ConflictError extends APIError<409, Headers> {} - -export class UnprocessableEntityError extends APIError<422, Headers> {} - -export class RateLimitError extends APIError<429, Headers> {} - -export class InternalServerError extends APIError {} diff --git a/src/core/fetcher/APIResponse.ts b/src/core/fetcher/APIResponse.ts new file mode 100644 index 0000000..dd4b946 --- /dev/null +++ b/src/core/fetcher/APIResponse.ts @@ -0,0 +1,23 @@ +import { RawResponse } from "./RawResponse.js"; + +/** + * The response of an API call. + * It is a successful response or a failed response. + */ +export type APIResponse = SuccessfulResponse | FailedResponse; + +export interface SuccessfulResponse { + ok: true; + body: T; + /** + * @deprecated Use `rawResponse` instead + */ + headers?: Record; + rawResponse: RawResponse; +} + +export interface FailedResponse { + ok: false; + error: T; + rawResponse: RawResponse; +} diff --git a/src/core/fetcher/BinaryResponse.ts b/src/core/fetcher/BinaryResponse.ts new file mode 100644 index 0000000..614cb59 --- /dev/null +++ b/src/core/fetcher/BinaryResponse.ts @@ -0,0 +1,36 @@ +import { ResponseWithBody } from "./ResponseWithBody.js"; + +export type BinaryResponse = { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ + bodyUsed: boolean; + /** + * Returns a ReadableStream of the response body. + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) + */ + stream: () => ReadableStream; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ + arrayBuffer: () => Promise; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ + blob: () => Promise; + /** + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) + * Some versions of the Fetch API may not support this method. + */ + bytes?(): Promise; +}; + +export function getBinaryResponse(response: ResponseWithBody): BinaryResponse { + const binaryResponse: BinaryResponse = { + get bodyUsed() { + return response.bodyUsed; + }, + stream: () => response.body, + arrayBuffer: response.arrayBuffer.bind(response), + blob: response.blob.bind(response), + }; + if ("bytes" in response && typeof response.bytes === "function") { + binaryResponse.bytes = response.bytes.bind(response); + } + + return binaryResponse; +} diff --git a/src/core/fetcher/Fetcher.ts b/src/core/fetcher/Fetcher.ts new file mode 100644 index 0000000..9e58ba7 --- /dev/null +++ b/src/core/fetcher/Fetcher.ts @@ -0,0 +1,163 @@ +import { toJson } from "../json.js"; +import { APIResponse } from "./APIResponse.js"; +import { createRequestUrl } from "./createRequestUrl.js"; +import { getErrorResponseBody } from "./getErrorResponseBody.js"; +import { getFetchFn } from "./getFetchFn.js"; +import { getRequestBody } from "./getRequestBody.js"; +import { getResponseBody } from "./getResponseBody.js"; +import { makeRequest } from "./makeRequest.js"; +import { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; +import { requestWithRetries } from "./requestWithRetries.js"; +import { Supplier } from "./Supplier.js"; + +export type FetchFunction = (args: Fetcher.Args) => Promise>; + +export declare namespace Fetcher { + export interface Args { + url: string; + method: string; + contentType?: string; + headers?: Record | undefined>; + queryParameters?: Record; + body?: unknown; + timeoutMs?: number; + maxRetries?: number; + withCredentials?: boolean; + abortSignal?: AbortSignal; + requestType?: "json" | "file" | "bytes"; + responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer" | "binary-response"; + duplex?: "half"; + } + + export type Error = FailedStatusCodeError | NonJsonError | TimeoutError | UnknownError; + + export interface FailedStatusCodeError { + reason: "status-code"; + statusCode: number; + body: unknown; + } + + export interface NonJsonError { + reason: "non-json"; + statusCode: number; + rawBody: string; + } + + export interface TimeoutError { + reason: "timeout"; + } + + export interface UnknownError { + reason: "unknown"; + errorMessage: string; + } +} + +async function getHeaders(args: Fetcher.Args): Promise> { + const newHeaders: Record = {}; + if (args.body !== undefined && args.contentType != null) { + newHeaders["Content-Type"] = args.contentType; + } + + if (args.headers == null) { + return newHeaders; + } + + for (const [key, value] of Object.entries(args.headers)) { + const result = await Supplier.get(value); + if (typeof result === "string") { + newHeaders[key] = result; + continue; + } + if (result == null) { + continue; + } + newHeaders[key] = `${result}`; + } + return newHeaders; +} + +export async function fetcherImpl(args: Fetcher.Args): Promise> { + const url = createRequestUrl(args.url, args.queryParameters); + const requestBody: BodyInit | undefined = await getRequestBody({ + body: args.body, + type: args.requestType === "json" ? "json" : "other", + }); + const fetchFn = await getFetchFn(); + + try { + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + url, + args.method, + await getHeaders(args), + requestBody, + args.timeoutMs, + args.abortSignal, + args.withCredentials, + args.duplex, + ), + args.maxRetries, + ); + + if (response.status >= 200 && response.status < 400) { + return { + ok: true, + body: (await getResponseBody(response, args.responseType)) as R, + headers: response.headers, + rawResponse: toRawResponse(response), + }; + } else { + return { + ok: false, + error: { + reason: "status-code", + statusCode: response.status, + body: await getErrorResponseBody(response), + }, + rawResponse: toRawResponse(response), + }; + } + } catch (error) { + if (args.abortSignal != null && args.abortSignal.aborted) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: "The user aborted a request", + }, + rawResponse: abortRawResponse, + }; + } else if (error instanceof Error && error.name === "AbortError") { + return { + ok: false, + error: { + reason: "timeout", + }, + rawResponse: abortRawResponse, + }; + } else if (error instanceof Error) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: error.message, + }, + rawResponse: unknownRawResponse, + }; + } + + return { + ok: false, + error: { + reason: "unknown", + errorMessage: toJson(error), + }, + rawResponse: unknownRawResponse, + }; + } +} + +export const fetcher: FetchFunction = fetcherImpl; diff --git a/src/core/fetcher/Headers.ts b/src/core/fetcher/Headers.ts new file mode 100644 index 0000000..af841aa --- /dev/null +++ b/src/core/fetcher/Headers.ts @@ -0,0 +1,93 @@ +let Headers: typeof globalThis.Headers; + +if (typeof globalThis.Headers !== "undefined") { + Headers = globalThis.Headers; +} else { + Headers = class Headers implements Headers { + private headers: Map; + + constructor(init?: HeadersInit) { + this.headers = new Map(); + + if (init) { + if (init instanceof Headers) { + init.forEach((value, key) => this.append(key, value)); + } else if (Array.isArray(init)) { + for (const [key, value] of init) { + if (typeof key === "string" && typeof value === "string") { + this.append(key, value); + } else { + throw new TypeError("Each header entry must be a [string, string] tuple"); + } + } + } else { + for (const [key, value] of Object.entries(init)) { + if (typeof value === "string") { + this.append(key, value); + } else { + throw new TypeError("Header values must be strings"); + } + } + } + } + } + + append(name: string, value: string): void { + const key = name.toLowerCase(); + const existing = this.headers.get(key) || []; + this.headers.set(key, [...existing, value]); + } + + delete(name: string): void { + const key = name.toLowerCase(); + this.headers.delete(key); + } + + get(name: string): string | null { + const key = name.toLowerCase(); + const values = this.headers.get(key); + return values ? values.join(", ") : null; + } + + has(name: string): boolean { + const key = name.toLowerCase(); + return this.headers.has(key); + } + + set(name: string, value: string): void { + const key = name.toLowerCase(); + this.headers.set(key, [value]); + } + + forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: unknown): void { + const boundCallback = thisArg ? callbackfn.bind(thisArg) : callbackfn; + this.headers.forEach((values, key) => boundCallback(values.join(", "), key, this)); + } + + getSetCookie(): string[] { + return this.headers.get("set-cookie") || []; + } + + *entries(): HeadersIterator<[string, string]> { + for (const [key, values] of this.headers.entries()) { + yield [key, values.join(", ")]; + } + } + + *keys(): HeadersIterator { + yield* this.headers.keys(); + } + + *values(): HeadersIterator { + for (const values of this.headers.values()) { + yield values.join(", "); + } + } + + [Symbol.iterator](): HeadersIterator<[string, string]> { + return this.entries(); + } + }; +} + +export { Headers }; diff --git a/src/core/fetcher/HttpResponsePromise.ts b/src/core/fetcher/HttpResponsePromise.ts new file mode 100644 index 0000000..026d88f --- /dev/null +++ b/src/core/fetcher/HttpResponsePromise.ts @@ -0,0 +1,116 @@ +import { WithRawResponse } from "./RawResponse.js"; + +/** + * A promise that returns the parsed response and lets you retrieve the raw response too. + */ +export class HttpResponsePromise extends Promise { + private innerPromise: Promise>; + private unwrappedPromise: Promise | undefined; + + private constructor(promise: Promise>) { + // Initialize with a no-op to avoid premature parsing + super((resolve) => { + resolve(undefined as unknown as T); + }); + this.innerPromise = promise; + } + + /** + * Creates an `HttpResponsePromise` from a function that returns a promise. + * + * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. + * @param args - Arguments to pass to the function. + * @returns An `HttpResponsePromise` instance. + */ + public static fromFunction Promise>, T>( + fn: F, + ...args: Parameters + ): HttpResponsePromise { + return new HttpResponsePromise(fn(...args)); + } + + /** + * Creates a function that returns an `HttpResponsePromise` from a function that returns a promise. + * + * @param fn - A function that returns a promise resolving to a `WithRawResponse` object. + * @returns A function that returns an `HttpResponsePromise` instance. + */ + public static interceptFunction< + F extends (...args: never[]) => Promise>, + T = Awaited>["data"], + >(fn: F): (...args: Parameters) => HttpResponsePromise { + return (...args: Parameters): HttpResponsePromise => { + return HttpResponsePromise.fromPromise(fn(...args)); + }; + } + + /** + * Creates an `HttpResponsePromise` from an existing promise. + * + * @param promise - A promise resolving to a `WithRawResponse` object. + * @returns An `HttpResponsePromise` instance. + */ + public static fromPromise(promise: Promise>): HttpResponsePromise { + return new HttpResponsePromise(promise); + } + + /** + * Creates an `HttpResponsePromise` from an executor function. + * + * @param executor - A function that takes resolve and reject callbacks to create a promise. + * @returns An `HttpResponsePromise` instance. + */ + public static fromExecutor( + executor: (resolve: (value: WithRawResponse) => void, reject: (reason?: unknown) => void) => void, + ): HttpResponsePromise { + const promise = new Promise>(executor); + return new HttpResponsePromise(promise); + } + + /** + * Creates an `HttpResponsePromise` from a resolved result. + * + * @param result - A `WithRawResponse` object to resolve immediately. + * @returns An `HttpResponsePromise` instance. + */ + public static fromResult(result: WithRawResponse): HttpResponsePromise { + const promise = Promise.resolve(result); + return new HttpResponsePromise(promise); + } + + private unwrap(): Promise { + if (!this.unwrappedPromise) { + this.unwrappedPromise = this.innerPromise.then(({ data }) => data); + } + return this.unwrappedPromise; + } + + /** @inheritdoc */ + public override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, + ): Promise { + return this.unwrap().then(onfulfilled, onrejected); + } + + /** @inheritdoc */ + public override catch( + onrejected?: ((reason: unknown) => TResult | PromiseLike) | null, + ): Promise { + return this.unwrap().catch(onrejected); + } + + /** @inheritdoc */ + public override finally(onfinally?: (() => void) | null): Promise { + return this.unwrap().finally(onfinally); + } + + /** + * Retrieves the data and raw response. + * + * @returns A promise resolving to a `WithRawResponse` object. + */ + public async withRawResponse(): Promise> { + return await this.innerPromise; + } +} diff --git a/src/core/fetcher/RawResponse.ts b/src/core/fetcher/RawResponse.ts new file mode 100644 index 0000000..37fb44e --- /dev/null +++ b/src/core/fetcher/RawResponse.ts @@ -0,0 +1,61 @@ +import { Headers } from "./Headers.js"; + +/** + * The raw response from the fetch call excluding the body. + */ +export type RawResponse = Omit< + { + [K in keyof Response as Response[K] extends Function ? never : K]: Response[K]; // strips out functions + }, + "ok" | "body" | "bodyUsed" +>; // strips out body and bodyUsed + +/** + * A raw response indicating that the request was aborted. + */ +export const abortRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 499, + statusText: "Client Closed Request", + type: "error", + url: "", +} as const; + +/** + * A raw response indicating an unknown error. + */ +export const unknownRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 0, + statusText: "Unknown Error", + type: "error", + url: "", +} as const; + +/** + * Converts a `RawResponse` object into a `RawResponse` by extracting its properties, + * excluding the `body` and `bodyUsed` fields. + * + * @param response - The `RawResponse` object to convert. + * @returns A `RawResponse` object containing the extracted properties of the input response. + */ +export function toRawResponse(response: Response): RawResponse { + return { + headers: response.headers, + redirected: response.redirected, + status: response.status, + statusText: response.statusText, + type: response.type, + url: response.url, + }; +} + +/** + * Creates a `RawResponse` from a standard `Response` object. + */ +export interface WithRawResponse { + readonly data: T; + readonly rawResponse: RawResponse; +} diff --git a/src/core/fetcher/ResponseWithBody.ts b/src/core/fetcher/ResponseWithBody.ts new file mode 100644 index 0000000..445d40f --- /dev/null +++ b/src/core/fetcher/ResponseWithBody.ts @@ -0,0 +1,7 @@ +export type ResponseWithBody = Response & { + body: ReadableStream; +}; + +export function isResponseWithBody(response: Response): response is ResponseWithBody { + return (response as ResponseWithBody).body != null; +} diff --git a/src/core/fetcher/Supplier.ts b/src/core/fetcher/Supplier.ts new file mode 100644 index 0000000..867c931 --- /dev/null +++ b/src/core/fetcher/Supplier.ts @@ -0,0 +1,11 @@ +export type Supplier = T | Promise | (() => T | Promise); + +export const Supplier = { + get: async (supplier: Supplier): Promise => { + if (typeof supplier === "function") { + return (supplier as () => T)(); + } else { + return supplier; + } + }, +}; diff --git a/src/core/fetcher/createRequestUrl.ts b/src/core/fetcher/createRequestUrl.ts new file mode 100644 index 0000000..88e1326 --- /dev/null +++ b/src/core/fetcher/createRequestUrl.ts @@ -0,0 +1,6 @@ +import { toQueryString } from "../url/qs.js"; + +export function createRequestUrl(baseUrl: string, queryParameters?: Record): string { + const queryString = toQueryString(queryParameters, { arrayFormat: "repeat" }); + return queryString ? `${baseUrl}?${queryString}` : baseUrl; +} diff --git a/src/core/fetcher/getErrorResponseBody.ts b/src/core/fetcher/getErrorResponseBody.ts new file mode 100644 index 0000000..450424b --- /dev/null +++ b/src/core/fetcher/getErrorResponseBody.ts @@ -0,0 +1,32 @@ +import { fromJson } from "../json.js"; +import { getResponseBody } from "./getResponseBody.js"; + +export async function getErrorResponseBody(response: Response): Promise { + let contentType = response.headers.get("Content-Type")?.toLowerCase(); + if (contentType == null || contentType.length === 0) { + return getResponseBody(response); + } + + if (contentType.indexOf(";") !== -1) { + contentType = contentType.split(";")[0]?.trim() ?? ""; + } + switch (contentType) { + case "application/hal+json": + case "application/json": + case "application/ld+json": + case "application/problem+json": + case "application/vnd.api+json": + case "text/json": + const text = await response.text(); + return text.length > 0 ? fromJson(text) : undefined; + default: + if (contentType.startsWith("application/vnd.") && contentType.endsWith("+json")) { + const text = await response.text(); + return text.length > 0 ? fromJson(text) : undefined; + } + + // Fallback to plain text if content type is not recognized + // Even if no body is present, the response will be an empty string + return await response.text(); + } +} diff --git a/src/core/fetcher/getFetchFn.ts b/src/core/fetcher/getFetchFn.ts new file mode 100644 index 0000000..9f845b9 --- /dev/null +++ b/src/core/fetcher/getFetchFn.ts @@ -0,0 +1,3 @@ +export async function getFetchFn(): Promise { + return fetch; +} diff --git a/src/core/fetcher/getHeader.ts b/src/core/fetcher/getHeader.ts new file mode 100644 index 0000000..50f922b --- /dev/null +++ b/src/core/fetcher/getHeader.ts @@ -0,0 +1,8 @@ +export function getHeader(headers: Record, header: string): string | undefined { + for (const [headerKey, headerValue] of Object.entries(headers)) { + if (headerKey.toLowerCase() === header.toLowerCase()) { + return headerValue; + } + } + return undefined; +} diff --git a/src/core/fetcher/getRequestBody.ts b/src/core/fetcher/getRequestBody.ts new file mode 100644 index 0000000..e38457c --- /dev/null +++ b/src/core/fetcher/getRequestBody.ts @@ -0,0 +1,16 @@ +import { toJson } from "../json.js"; + +export declare namespace GetRequestBody { + interface Args { + body: unknown; + type: "json" | "file" | "bytes" | "other"; + } +} + +export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { + if (type.includes("json")) { + return toJson(body); + } else { + return body as BodyInit; + } +} diff --git a/src/core/fetcher/getResponseBody.ts b/src/core/fetcher/getResponseBody.ts new file mode 100644 index 0000000..7ca8b3d --- /dev/null +++ b/src/core/fetcher/getResponseBody.ts @@ -0,0 +1,43 @@ +import { getBinaryResponse } from "./BinaryResponse.js"; +import { isResponseWithBody } from "./ResponseWithBody.js"; +import { fromJson } from "../json.js"; + +export async function getResponseBody(response: Response, responseType?: string): Promise { + if (!isResponseWithBody(response)) { + return undefined; + } + switch (responseType) { + case "binary-response": + return getBinaryResponse(response); + case "blob": + return await response.blob(); + case "arrayBuffer": + return await response.arrayBuffer(); + case "sse": + return response.body; + case "streaming": + return response.body; + + case "text": + return await response.text(); + } + + // if responseType is "json" or not specified, try to parse as JSON + const text = await response.text(); + if (text.length > 0) { + try { + let responseBody = fromJson(text); + return responseBody; + } catch (err) { + return { + ok: false, + error: { + reason: "non-json", + statusCode: response.status, + rawBody: text, + }, + }; + } + } + return undefined; +} diff --git a/src/core/fetcher/index.ts b/src/core/fetcher/index.ts new file mode 100644 index 0000000..a131e34 --- /dev/null +++ b/src/core/fetcher/index.ts @@ -0,0 +1,9 @@ +export type { APIResponse } from "./APIResponse.js"; +export type { BinaryResponse } from "./BinaryResponse.js"; +export type { Fetcher, FetchFunction } from "./Fetcher.js"; +export { fetcher } from "./Fetcher.js"; +export { getHeader } from "./getHeader.js"; +export { HttpResponsePromise } from "./HttpResponsePromise.js"; +export type { RawResponse, WithRawResponse } from "./RawResponse.js"; +export { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js"; +export { Supplier } from "./Supplier.js"; diff --git a/src/core/fetcher/makeRequest.ts b/src/core/fetcher/makeRequest.ts new file mode 100644 index 0000000..1a5ffd3 --- /dev/null +++ b/src/core/fetcher/makeRequest.ts @@ -0,0 +1,44 @@ +import { anySignal, getTimeoutSignal } from "./signals.js"; + +export const makeRequest = async ( + fetchFn: (url: string, init: RequestInit) => Promise, + url: string, + method: string, + headers: Record, + requestBody: BodyInit | undefined, + timeoutMs?: number, + abortSignal?: AbortSignal, + withCredentials?: boolean, + duplex?: "half", +): Promise => { + const signals: AbortSignal[] = []; + + // Add timeout signal + let timeoutAbortId: NodeJS.Timeout | undefined = undefined; + if (timeoutMs != null) { + const { signal, abortId } = getTimeoutSignal(timeoutMs); + timeoutAbortId = abortId; + signals.push(signal); + } + + // Add arbitrary signal + if (abortSignal != null) { + signals.push(abortSignal); + } + let newSignals = anySignal(signals); + const response = await fetchFn(url, { + method: method, + headers, + body: requestBody, + signal: newSignals, + credentials: withCredentials ? "include" : undefined, + // @ts-ignore + duplex, + }); + + if (timeoutAbortId != null) { + clearTimeout(timeoutAbortId); + } + + return response; +}; diff --git a/src/core/fetcher/requestWithRetries.ts b/src/core/fetcher/requestWithRetries.ts new file mode 100644 index 0000000..add3cce --- /dev/null +++ b/src/core/fetcher/requestWithRetries.ts @@ -0,0 +1,33 @@ +const INITIAL_RETRY_DELAY = 1000; // in milliseconds +const MAX_RETRY_DELAY = 60000; // in milliseconds +const DEFAULT_MAX_RETRIES = 2; +const JITTER_FACTOR = 0.2; // 20% random jitter + +function addJitter(delay: number): number { + // Generate a random value between -JITTER_FACTOR and +JITTER_FACTOR + const jitterMultiplier = 1 + (Math.random() * 2 - 1) * JITTER_FACTOR; + return delay * jitterMultiplier; +} + +export async function requestWithRetries( + requestFn: () => Promise, + maxRetries: number = DEFAULT_MAX_RETRIES, +): Promise { + let response: Response = await requestFn(); + + for (let i = 0; i < maxRetries; ++i) { + if ([408, 429].includes(response.status) || response.status >= 500) { + // Calculate base delay using exponential backoff (in milliseconds) + const baseDelay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, i), MAX_RETRY_DELAY); + + // Add jitter to the delay + const delayWithJitter = addJitter(baseDelay); + + await new Promise((resolve) => setTimeout(resolve, delayWithJitter)); + response = await requestFn(); + } else { + break; + } + } + return response!; +} diff --git a/src/core/fetcher/signals.ts b/src/core/fetcher/signals.ts new file mode 100644 index 0000000..a8d32a2 --- /dev/null +++ b/src/core/fetcher/signals.ts @@ -0,0 +1,38 @@ +const TIMEOUT = "timeout"; + +export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: NodeJS.Timeout } { + const controller = new AbortController(); + const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); + return { signal: controller.signal, abortId }; +} + +/** + * Returns an abort signal that is getting aborted when + * at least one of the specified abort signals is aborted. + * + * Requires at least node.js 18. + */ +export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { + // Allowing signals to be passed either as array + // of signals or as multiple arguments. + const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args) as AbortSignal[]; + + const controller = new AbortController(); + + for (const signal of signals) { + if (signal.aborted) { + // Exiting early if one of the signals + // is already aborted. + controller.abort((signal as any)?.reason); + break; + } + + // Listening for signals and removing the listeners + // when at least one symbol is aborted. + signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { + signal: controller.signal, + }); + } + + return controller.signal; +} diff --git a/src/core/headers.ts b/src/core/headers.ts new file mode 100644 index 0000000..561314d --- /dev/null +++ b/src/core/headers.ts @@ -0,0 +1,35 @@ +import * as core from "./index.js"; + +export function mergeHeaders( + ...headersArray: (Record | undefined> | undefined)[] +): Record> { + const result: Record> = {}; + + for (const [key, value] of headersArray + .filter((headers) => headers != null) + .flatMap((headers) => Object.entries(headers))) { + if (value != null) { + result[key] = value; + } else if (key in result) { + delete result[key]; + } + } + + return result; +} + +export function mergeOnlyDefinedHeaders( + ...headersArray: (Record | undefined> | undefined)[] +): Record> { + const result: Record> = {}; + + for (const [key, value] of headersArray + .filter((headers) => headers != null) + .flatMap((headers) => Object.entries(headers))) { + if (value != null) { + result[key] = value; + } + } + + return result; +} diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..bbb640d --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,3 @@ +export * from "./fetcher/index.js"; +export * from "./runtime/index.js"; +export * as url from "./url/index.js"; diff --git a/src/core/json.ts b/src/core/json.ts new file mode 100644 index 0000000..c052f32 --- /dev/null +++ b/src/core/json.ts @@ -0,0 +1,27 @@ +/** + * Serialize a value to JSON + * @param value A JavaScript value, usually an object or array, to be converted. + * @param replacer A function that transforms the results. + * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. + * @returns JSON string + */ +export const toJson = ( + value: unknown, + replacer?: (this: unknown, key: string, value: unknown) => unknown, + space?: string | number, +): string => { + return JSON.stringify(value, replacer, space); +}; + +/** + * Parse JSON string to object, array, or other type + * @param text A valid JSON string. + * @param reviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is. + * @returns Parsed object, array, or other type + */ +export function fromJson( + text: string, + reviver?: (this: unknown, key: string, value: unknown) => unknown, +): T { + return JSON.parse(text, reviver); +} diff --git a/src/core/resource.ts b/src/core/resource.ts deleted file mode 100644 index c4140b2..0000000 --- a/src/core/resource.ts +++ /dev/null @@ -1,11 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { BrowserUse } from '../client'; - -export abstract class APIResource { - protected _client: BrowserUse; - - constructor(client: BrowserUse) { - this._client = client; - } -} diff --git a/src/core/runtime/index.ts b/src/core/runtime/index.ts new file mode 100644 index 0000000..cfab23f --- /dev/null +++ b/src/core/runtime/index.ts @@ -0,0 +1 @@ +export { RUNTIME } from "./runtime.js"; diff --git a/src/core/runtime/runtime.ts b/src/core/runtime/runtime.ts new file mode 100644 index 0000000..08fd256 --- /dev/null +++ b/src/core/runtime/runtime.ts @@ -0,0 +1,133 @@ +interface DenoGlobal { + version: { + deno: string; + }; +} + +interface BunGlobal { + version: string; +} + +declare const Deno: DenoGlobal | undefined; +declare const Bun: BunGlobal | undefined; +declare const EdgeRuntime: string | undefined; +declare const self: typeof globalThis.self & { + importScripts?: unknown; +}; + +/** + * A constant that indicates which environment and version the SDK is running in. + */ +export const RUNTIME: Runtime = evaluateRuntime(); + +export interface Runtime { + type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd" | "edge-runtime"; + version?: string; + parsedVersion?: number; +} + +function evaluateRuntime(): Runtime { + /** + * A constant that indicates whether the environment the code is running is a Web Browser. + */ + const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; + if (isBrowser) { + return { + type: "browser", + version: window.navigator.userAgent, + }; + } + + /** + * A constant that indicates whether the environment the code is running is Cloudflare. + * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent + */ + const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; + if (isCloudflare) { + return { + type: "workerd", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Edge Runtime. + * https://vercel.com/docs/functions/runtimes/edge-runtime#check-if-you're-running-on-the-edge-runtime + */ + const isEdgeRuntime = typeof EdgeRuntime === "string"; + if (isEdgeRuntime) { + return { + type: "edge-runtime", + }; + } + + /** + * A constant that indicates whether the environment the code is running is a Web Worker. + */ + const isWebWorker = + typeof self === "object" && + typeof self?.importScripts === "function" && + (self.constructor?.name === "DedicatedWorkerGlobalScope" || + self.constructor?.name === "ServiceWorkerGlobalScope" || + self.constructor?.name === "SharedWorkerGlobalScope"); + if (isWebWorker) { + return { + type: "web-worker", + }; + } + + /** + * A constant that indicates whether the environment the code is running is Deno. + * FYI Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions + */ + const isDeno = + typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; + if (isDeno) { + return { + type: "deno", + version: Deno.version.deno, + }; + } + + /** + * A constant that indicates whether the environment the code is running is Bun.sh. + */ + const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; + if (isBun) { + return { + type: "bun", + version: Bun.version, + }; + } + + /** + * A constant that indicates whether the environment the code is running is Node.JS. + */ + const isNode = + typeof process !== "undefined" && + "version" in process && + !!process.version && + "versions" in process && + !!process.versions?.node; + if (isNode) { + return { + type: "node", + version: process.versions.node, + parsedVersion: Number(process.versions.node.split(".")[0]), + }; + } + + /** + * A constant that indicates whether the environment the code is running is in React-Native. + * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js + */ + const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; + if (isReactNative) { + return { + type: "react-native", + }; + } + + return { + type: "unknown", + }; +} diff --git a/src/core/uploads.ts b/src/core/uploads.ts deleted file mode 100644 index 2882ca6..0000000 --- a/src/core/uploads.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { type Uploadable } from '../internal/uploads'; -export { toFile, type ToFileInput } from '../internal/to-file'; diff --git a/src/core/url/index.ts b/src/core/url/index.ts new file mode 100644 index 0000000..ed5aa0f --- /dev/null +++ b/src/core/url/index.ts @@ -0,0 +1,2 @@ +export { join } from "./join.js"; +export { toQueryString } from "./qs.js"; diff --git a/src/core/url/join.ts b/src/core/url/join.ts new file mode 100644 index 0000000..200426b --- /dev/null +++ b/src/core/url/join.ts @@ -0,0 +1,80 @@ +export function join(base: string, ...segments: string[]): string { + if (!base) { + return ""; + } + + if (segments.length === 0) { + return base; + } + + if (base.includes("://")) { + let url: URL; + try { + url = new URL(base); + } catch { + // Fallback to path joining if URL is malformed + return joinPath(base, ...segments); + } + + const lastSegment = segments[segments.length - 1]; + const shouldPreserveTrailingSlash = lastSegment && lastSegment.endsWith("/"); + + for (const segment of segments) { + const cleanSegment = trimSlashes(segment); + if (cleanSegment) { + url.pathname = joinPathSegments(url.pathname, cleanSegment); + } + } + + if (shouldPreserveTrailingSlash && !url.pathname.endsWith("/")) { + url.pathname += "/"; + } + + return url.toString(); + } + + return joinPath(base, ...segments); +} + +function joinPath(base: string, ...segments: string[]): string { + if (segments.length === 0) { + return base; + } + + let result = base; + + const lastSegment = segments[segments.length - 1]; + const shouldPreserveTrailingSlash = lastSegment && lastSegment.endsWith("/"); + + for (const segment of segments) { + const cleanSegment = trimSlashes(segment); + if (cleanSegment) { + result = joinPathSegments(result, cleanSegment); + } + } + + if (shouldPreserveTrailingSlash && !result.endsWith("/")) { + result += "/"; + } + + return result; +} + +function joinPathSegments(left: string, right: string): string { + if (left.endsWith("/")) { + return left + right; + } + return left + "/" + right; +} + +function trimSlashes(str: string): string { + if (!str) return str; + + let start = 0; + let end = str.length; + + if (str.startsWith("/")) start = 1; + if (str.endsWith("/")) end = str.length - 1; + + return start === 0 && end === str.length ? str : str.slice(start, end); +} diff --git a/src/core/url/qs.ts b/src/core/url/qs.ts new file mode 100644 index 0000000..13e89be --- /dev/null +++ b/src/core/url/qs.ts @@ -0,0 +1,74 @@ +interface QueryStringOptions { + arrayFormat?: "indices" | "repeat"; + encode?: boolean; +} + +const defaultQsOptions: Required = { + arrayFormat: "indices", + encode: true, +} as const; + +function encodeValue(value: unknown, shouldEncode: boolean): string { + if (value === undefined) { + return ""; + } + if (value === null) { + return ""; + } + const stringValue = String(value); + return shouldEncode ? encodeURIComponent(stringValue) : stringValue; +} + +function stringifyObject(obj: Record, prefix = "", options: Required): string[] { + const parts: string[] = []; + + for (const [key, value] of Object.entries(obj)) { + const fullKey = prefix ? `${prefix}[${key}]` : key; + + if (value === undefined) { + continue; + } + + if (Array.isArray(value)) { + if (value.length === 0) { + continue; + } + for (let i = 0; i < value.length; i++) { + const item = value[i]; + if (item === undefined) { + continue; + } + if (typeof item === "object" && !Array.isArray(item) && item !== null) { + const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; + parts.push(...stringifyObject(item as Record, arrayKey, options)); + } else { + const arrayKey = options.arrayFormat === "indices" ? `${fullKey}[${i}]` : fullKey; + const encodedKey = options.encode ? encodeURIComponent(arrayKey) : arrayKey; + parts.push(`${encodedKey}=${encodeValue(item, options.encode)}`); + } + } + } else if (typeof value === "object" && value !== null) { + if (Object.keys(value as Record).length === 0) { + continue; + } + parts.push(...stringifyObject(value as Record, fullKey, options)); + } else { + const encodedKey = options.encode ? encodeURIComponent(fullKey) : fullKey; + parts.push(`${encodedKey}=${encodeValue(value, options.encode)}`); + } + } + + return parts; +} + +export function toQueryString(obj: unknown, options?: QueryStringOptions): string { + if (obj == null || typeof obj !== "object") { + return ""; + } + + const parts = stringifyObject(obj as Record, "", { + ...defaultQsOptions, + ...options, + }); + return parts.join("&"); +} diff --git a/src/error.ts b/src/error.ts deleted file mode 100644 index fc55f46..0000000 --- a/src/error.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/error instead */ -export * from './core/error'; diff --git a/src/errors/BrowserUseError.ts b/src/errors/BrowserUseError.ts new file mode 100644 index 0000000..a1407b0 --- /dev/null +++ b/src/errors/BrowserUseError.ts @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../core/index.js"; +import { toJson } from "../core/json.js"; + +export class BrowserUseError extends Error { + public readonly statusCode?: number; + public readonly body?: unknown; + public readonly rawResponse?: core.RawResponse; + + constructor({ + message, + statusCode, + body, + rawResponse, + }: { + message?: string; + statusCode?: number; + body?: unknown; + rawResponse?: core.RawResponse; + }) { + super(buildMessage({ message, statusCode, body })); + Object.setPrototypeOf(this, BrowserUseError.prototype); + this.statusCode = statusCode; + this.body = body; + this.rawResponse = rawResponse; + } +} + +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { + let lines: string[] = []; + if (message != null) { + lines.push(message); + } + + if (statusCode != null) { + lines.push(`Status code: ${statusCode.toString()}`); + } + + if (body != null) { + lines.push(`Body: ${toJson(body, undefined, 2)}`); + } + + return lines.join("\n"); +} diff --git a/src/errors/BrowserUseTimeoutError.ts b/src/errors/BrowserUseTimeoutError.ts new file mode 100644 index 0000000..9d69863 --- /dev/null +++ b/src/errors/BrowserUseTimeoutError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export class BrowserUseTimeoutError extends Error { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, BrowserUseTimeoutError.prototype); + } +} diff --git a/src/errors/index.ts b/src/errors/index.ts new file mode 100644 index 0000000..723a384 --- /dev/null +++ b/src/errors/index.ts @@ -0,0 +1,2 @@ +export { BrowserUseError } from "./BrowserUseError.js"; +export { BrowserUseTimeoutError } from "./BrowserUseTimeoutError.js"; diff --git a/src/index.ts b/src/index.ts index 705d0f7..565e562 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,22 +1,3 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { BrowserUse as default } from './client'; - -export { type Uploadable, toFile } from './core/uploads'; -export { APIPromise } from './core/api-promise'; -export { BrowserUse, type ClientOptions } from './client'; -export { - BrowserUseError, - APIError, - APIConnectionError, - APIConnectionTimeoutError, - APIUserAbortError, - NotFoundError, - ConflictError, - RateLimitError, - BadRequestError, - AuthenticationError, - InternalServerError, - PermissionDeniedError, - UnprocessableEntityError, -} from './core/error'; +export * as BrowserUse from "./api/index.js"; +export { BrowserUseError, BrowserUseTimeoutError } from "./errors/index.js"; +export { BrowserUseClient } from "./Client.js"; diff --git a/src/internal/README.md b/src/internal/README.md deleted file mode 100644 index 3ef5a25..0000000 --- a/src/internal/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `internal` - -The modules in this directory are not importable outside this package and will change between releases. diff --git a/src/internal/builtin-types.ts b/src/internal/builtin-types.ts deleted file mode 100644 index c23d3bd..0000000 --- a/src/internal/builtin-types.ts +++ /dev/null @@ -1,93 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise; - -/** - * An alias to the builtin `RequestInit` type so we can - * easily alias it in import statements if there are name clashes. - * - * https://developer.mozilla.org/docs/Web/API/RequestInit - */ -type _RequestInit = RequestInit; - -/** - * An alias to the builtin `Response` type so we can - * easily alias it in import statements if there are name clashes. - * - * https://developer.mozilla.org/docs/Web/API/Response - */ -type _Response = Response; - -/** - * The type for the first argument to `fetch`. - * - * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource - */ -type _RequestInfo = Request | URL | string; - -/** - * The type for constructing `RequestInit` Headers. - * - * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers - */ -type _HeadersInit = RequestInit['headers']; - -/** - * The type for constructing `RequestInit` body. - * - * https://developer.mozilla.org/docs/Web/API/RequestInit#body - */ -type _BodyInit = RequestInit['body']; - -/** - * An alias to the builtin `Array` type so we can - * easily alias it in import statements if there are name clashes. - */ -type _Array = Array; - -/** - * An alias to the builtin `Record` type so we can - * easily alias it in import statements if there are name clashes. - */ -type _Record = Record; - -export type { - _Array as Array, - _BodyInit as BodyInit, - _HeadersInit as HeadersInit, - _Record as Record, - _RequestInfo as RequestInfo, - _RequestInit as RequestInit, - _Response as Response, -}; - -/** - * A copy of the builtin `EndingType` type as it isn't fully supported in certain - * environments and attempting to reference the global version will error. - * - * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941 - */ -type EndingType = 'native' | 'transparent'; - -/** - * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain - * environments and attempting to reference the global version will error. - * - * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154 - * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options - */ -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -/** - * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain - * environments and attempting to reference the global version will error. - * - * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503 - * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options - */ -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} diff --git a/src/internal/detect-platform.ts b/src/internal/detect-platform.ts deleted file mode 100644 index 7dbe981..0000000 --- a/src/internal/detect-platform.ts +++ /dev/null @@ -1,196 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { VERSION } from '../version'; - -export const isRunningInBrowser = () => { - return ( - // @ts-ignore - typeof window !== 'undefined' && - // @ts-ignore - typeof window.document !== 'undefined' && - // @ts-ignore - typeof navigator !== 'undefined' - ); -}; - -type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown'; - -/** - * Note this does not detect 'browser'; for that, use getBrowserInfo(). - */ -function getDetectedPlatform(): DetectedPlatform { - if (typeof Deno !== 'undefined' && Deno.build != null) { - return 'deno'; - } - if (typeof EdgeRuntime !== 'undefined') { - return 'edge'; - } - if ( - Object.prototype.toString.call( - typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0, - ) === '[object process]' - ) { - return 'node'; - } - return 'unknown'; -} - -declare const Deno: any; -declare const EdgeRuntime: any; -type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; -type PlatformName = - | 'MacOS' - | 'Linux' - | 'Windows' - | 'FreeBSD' - | 'OpenBSD' - | 'iOS' - | 'Android' - | `Other:${string}` - | 'Unknown'; -type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; -type PlatformProperties = { - 'X-Stainless-Lang': 'js'; - 'X-Stainless-Package-Version': string; - 'X-Stainless-OS': PlatformName; - 'X-Stainless-Arch': Arch; - 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; - 'X-Stainless-Runtime-Version': string; -}; -const getPlatformProperties = (): PlatformProperties => { - const detectedPlatform = getDetectedPlatform(); - if (detectedPlatform === 'deno') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform(Deno.build.os), - 'X-Stainless-Arch': normalizeArch(Deno.build.arch), - 'X-Stainless-Runtime': 'deno', - 'X-Stainless-Runtime-Version': - typeof Deno.version === 'string' ? Deno.version : (Deno.version?.deno ?? 'unknown'), - }; - } - if (typeof EdgeRuntime !== 'undefined') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': `other:${EdgeRuntime}`, - 'X-Stainless-Runtime': 'edge', - 'X-Stainless-Runtime-Version': (globalThis as any).process.version, - }; - } - // Check if Node.js - if (detectedPlatform === 'node') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'), - 'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'), - 'X-Stainless-Runtime': 'node', - 'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown', - }; - } - - const browserInfo = getBrowserInfo(); - if (browserInfo) { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, - 'X-Stainless-Runtime-Version': browserInfo.version, - }; - } - - // TODO add support for Cloudflare workers, etc. - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': 'unknown', - 'X-Stainless-Runtime-Version': 'unknown', - }; -}; - -type BrowserInfo = { - browser: Browser; - version: string; -}; - -declare const navigator: { userAgent: string } | undefined; - -// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts -function getBrowserInfo(): BrowserInfo | null { - if (typeof navigator === 'undefined' || !navigator) { - return null; - } - - // NOTE: The order matters here! - const browserPatterns = [ - { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, - ]; - - // Find the FIRST matching browser - for (const { key, pattern } of browserPatterns) { - const match = pattern.exec(navigator.userAgent); - if (match) { - const major = match[1] || 0; - const minor = match[2] || 0; - const patch = match[3] || 0; - - return { browser: key, version: `${major}.${minor}.${patch}` }; - } - } - - return null; -} - -const normalizeArch = (arch: string): Arch => { - // Node docs: - // - https://nodejs.org/api/process.html#processarch - // Deno docs: - // - https://doc.deno.land/deno/stable/~/Deno.build - if (arch === 'x32') return 'x32'; - if (arch === 'x86_64' || arch === 'x64') return 'x64'; - if (arch === 'arm') return 'arm'; - if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; - if (arch) return `other:${arch}`; - return 'unknown'; -}; - -const normalizePlatform = (platform: string): PlatformName => { - // Node platforms: - // - https://nodejs.org/api/process.html#processplatform - // Deno platforms: - // - https://doc.deno.land/deno/stable/~/Deno.build - // - https://github.com/denoland/deno/issues/14799 - - platform = platform.toLowerCase(); - - // NOTE: this iOS check is untested and may not work - // Node does not work natively on IOS, there is a fork at - // https://github.com/nodejs-mobile/nodejs-mobile - // however it is unknown at the time of writing how to detect if it is running - if (platform.includes('ios')) return 'iOS'; - if (platform === 'android') return 'Android'; - if (platform === 'darwin') return 'MacOS'; - if (platform === 'win32') return 'Windows'; - if (platform === 'freebsd') return 'FreeBSD'; - if (platform === 'openbsd') return 'OpenBSD'; - if (platform === 'linux') return 'Linux'; - if (platform) return `Other:${platform}`; - return 'Unknown'; -}; - -let _platformHeaders: PlatformProperties; -export const getPlatformHeaders = () => { - return (_platformHeaders ??= getPlatformProperties()); -}; diff --git a/src/internal/errors.ts b/src/internal/errors.ts deleted file mode 100644 index 82c7b14..0000000 --- a/src/internal/errors.ts +++ /dev/null @@ -1,33 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export function isAbortError(err: unknown) { - return ( - typeof err === 'object' && - err !== null && - // Spec-compliant fetch implementations - (('name' in err && (err as any).name === 'AbortError') || - // Expo fetch - ('message' in err && String((err as any).message).includes('FetchRequestCanceledException'))) - ); -} - -export const castToError = (err: any): Error => { - if (err instanceof Error) return err; - if (typeof err === 'object' && err !== null) { - try { - if (Object.prototype.toString.call(err) === '[object Error]') { - // @ts-ignore - not all envs have native support for cause yet - const error = new Error(err.message, err.cause ? { cause: err.cause } : {}); - if (err.stack) error.stack = err.stack; - // @ts-ignore - not all envs have native support for cause yet - if (err.cause && !error.cause) error.cause = err.cause; - if (err.name) error.name = err.name; - return error; - } - } catch {} - try { - return new Error(JSON.stringify(err)); - } catch {} - } - return new Error(err); -}; diff --git a/src/internal/headers.ts b/src/internal/headers.ts deleted file mode 100644 index c724a9d..0000000 --- a/src/internal/headers.ts +++ /dev/null @@ -1,97 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isReadonlyArray } from './utils/values'; - -type HeaderValue = string | undefined | null; -export type HeadersLike = - | Headers - | readonly HeaderValue[][] - | Record - | undefined - | null - | NullableHeaders; - -const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); - -/** - * @internal - * Users can pass explicit nulls to unset default headers. When we parse them - * into a standard headers type we need to preserve that information. - */ -export type NullableHeaders = { - /** Brand check, prevent users from creating a NullableHeaders. */ - [brand_privateNullableHeaders]: true; - /** Parsed headers. */ - values: Headers; - /** Set of lowercase header names explicitly set to null. */ - nulls: Set; -}; - -function* iterateHeaders(headers: HeadersLike): IterableIterator { - if (!headers) return; - - if (brand_privateNullableHeaders in headers) { - const { values, nulls } = headers; - yield* values.entries(); - for (const name of nulls) { - yield [name, null]; - } - return; - } - - let shouldClear = false; - let iter: Iterable; - if (headers instanceof Headers) { - iter = headers.entries(); - } else if (isReadonlyArray(headers)) { - iter = headers; - } else { - shouldClear = true; - iter = Object.entries(headers ?? {}); - } - for (let row of iter) { - const name = row[0]; - if (typeof name !== 'string') throw new TypeError('expected header name to be a string'); - const values = isReadonlyArray(row[1]) ? row[1] : [row[1]]; - let didClear = false; - for (const value of values) { - if (value === undefined) continue; - - // Objects keys always overwrite older headers, they never append. - // Yield a null to clear the header before adding the new values. - if (shouldClear && !didClear) { - didClear = true; - yield [name, null]; - } - yield [name, value]; - } - } -} - -export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { - const targetHeaders = new Headers(); - const nullHeaders = new Set(); - for (const headers of newHeaders) { - const seenHeaders = new Set(); - for (const [name, value] of iterateHeaders(headers)) { - const lowerName = name.toLowerCase(); - if (!seenHeaders.has(lowerName)) { - targetHeaders.delete(name); - seenHeaders.add(lowerName); - } - if (value === null) { - targetHeaders.delete(name); - nullHeaders.add(lowerName); - } else { - targetHeaders.append(name, value); - nullHeaders.delete(lowerName); - } - } - } - return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; -}; - -export const isEmptyHeaders = (headers: HeadersLike) => { - for (const _ of iterateHeaders(headers)) return false; - return true; -}; diff --git a/src/internal/parse.ts b/src/internal/parse.ts deleted file mode 100644 index 4d951f8..0000000 --- a/src/internal/parse.ts +++ /dev/null @@ -1,50 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { FinalRequestOptions } from './request-options'; -import { type BrowserUse } from '../client'; -import { formatRequestDetails, loggerFor } from './utils/log'; - -export type APIResponseProps = { - response: Response; - options: FinalRequestOptions; - controller: AbortController; - requestLogID: string; - retryOfRequestLogID: string | undefined; - startTime: number; -}; - -export async function defaultParseResponse(client: BrowserUse, props: APIResponseProps): Promise { - const { response, requestLogID, retryOfRequestLogID, startTime } = props; - const body = await (async () => { - // fetch refuses to read the body when the status code is 204. - if (response.status === 204) { - return null as T; - } - - if (props.options.__binaryResponse) { - return response as unknown as T; - } - - const contentType = response.headers.get('content-type'); - const mediaType = contentType?.split(';')[0]?.trim(); - const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); - if (isJSON) { - const json = await response.json(); - return json as T; - } - - const text = await response.text(); - return text as unknown as T; - })(); - loggerFor(client).debug( - `[${requestLogID}] response parsed`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - body, - durationMs: Date.now() - startTime, - }), - ); - return body; -} diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts deleted file mode 100644 index 2aabf9a..0000000 --- a/src/internal/request-options.ts +++ /dev/null @@ -1,91 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { NullableHeaders } from './headers'; - -import type { BodyInit } from './builtin-types'; -import type { HTTPMethod, MergedRequestInit } from './types'; -import { type HeadersLike } from './headers'; - -export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; - -export type RequestOptions = { - /** - * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). - */ - method?: HTTPMethod; - - /** - * The URL path for the request. - * - * @example "/v1/foo" - */ - path?: string; - - /** - * Query parameters to include in the request URL. - */ - query?: object | undefined | null; - - /** - * The request body. Can be a string, JSON object, FormData, or other supported types. - */ - body?: unknown; - - /** - * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. - */ - headers?: HeadersLike; - - /** - * The maximum number of times that the client will retry a request in case of a - * temporary failure, like a network error or a 5XX error from the server. - * - * @default 2 - */ - maxRetries?: number; - - stream?: boolean | undefined; - - /** - * The maximum amount of time (in milliseconds) that the client should wait for a response - * from the server before timing out a single request. - * - * @unit milliseconds - */ - timeout?: number; - - /** - * Additional `RequestInit` options to be passed to the underlying `fetch` call. - * These options will be merged with the client's default fetch options. - */ - fetchOptions?: MergedRequestInit; - - /** - * An AbortSignal that can be used to cancel the request. - */ - signal?: AbortSignal | undefined | null; - - /** - * A unique key for this request to enable idempotency. - */ - idempotencyKey?: string; - - /** - * Override the default base URL for this specific request. - */ - defaultBaseURL?: string | undefined; - - __binaryResponse?: boolean | undefined; -}; - -export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; -export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; - -export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { - return { - bodyHeaders: { - 'content-type': 'application/json', - }, - body: JSON.stringify(body), - }; -}; diff --git a/src/internal/shim-types.ts b/src/internal/shim-types.ts deleted file mode 100644 index 8ddf7b0..0000000 --- a/src/internal/shim-types.ts +++ /dev/null @@ -1,26 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * Shims for types that we can't always rely on being available globally. - * - * Note: these only exist at the type-level, there is no corresponding runtime - * version for any of these symbols. - */ - -type NeverToAny = T extends never ? any : T; - -/** @ts-ignore */ -type _DOMReadableStream = globalThis.ReadableStream; - -/** @ts-ignore */ -type _NodeReadableStream = import('stream/web').ReadableStream; - -type _ConditionalNodeReadableStream = - typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream; - -type _ReadableStream = NeverToAny< - | ([0] extends [1 & _DOMReadableStream] ? never : _DOMReadableStream) - | ([0] extends [1 & _ConditionalNodeReadableStream] ? never : _ConditionalNodeReadableStream) ->; - -export type { _ReadableStream as ReadableStream }; diff --git a/src/internal/shims.ts b/src/internal/shims.ts deleted file mode 100644 index 0135f1a..0000000 --- a/src/internal/shims.ts +++ /dev/null @@ -1,107 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available. - * - * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error - * messages in cases where an environment isn't fully supported. - */ - -import type { Fetch } from './builtin-types'; -import type { ReadableStream } from './shim-types'; - -export function getDefaultFetch(): Fetch { - if (typeof fetch !== 'undefined') { - return fetch as any; - } - - throw new Error( - '`fetch` is not defined as a global; Either pass `fetch` to the client, `new BrowserUse({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', - ); -} - -type ReadableStreamArgs = ConstructorParameters; - -export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream { - const ReadableStream = (globalThis as any).ReadableStream; - if (typeof ReadableStream === 'undefined') { - // Note: All of the platforms / runtimes we officially support already define - // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes. - throw new Error( - '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`', - ); - } - - return new ReadableStream(...args); -} - -export function ReadableStreamFrom(iterable: Iterable | AsyncIterable): ReadableStream { - let iter: AsyncIterator | Iterator = - Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); - - return makeReadableStream({ - start() {}, - async pull(controller: any) { - const { done, value } = await iter.next(); - if (done) { - controller.close(); - } else { - controller.enqueue(value); - } - }, - async cancel() { - await iter.return?.(); - }, - }); -} - -/** - * Most browsers don't yet have async iterable support for ReadableStream, - * and Node has a very different way of reading bytes from its "ReadableStream". - * - * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 - */ -export function ReadableStreamToAsyncIterable(stream: any): AsyncIterableIterator { - if (stream[Symbol.asyncIterator]) return stream; - - const reader = stream.getReader(); - return { - async next() { - try { - const result = await reader.read(); - if (result?.done) reader.releaseLock(); // release lock when stream becomes closed - return result; - } catch (e) { - reader.releaseLock(); // release lock when stream becomes errored - throw e; - } - }, - async return() { - const cancelPromise = reader.cancel(); - reader.releaseLock(); - await cancelPromise; - return { done: true, value: undefined }; - }, - [Symbol.asyncIterator]() { - return this; - }, - }; -} - -/** - * Cancels a ReadableStream we don't need to consume. - * See https://undici.nodejs.org/#/?id=garbage-collection - */ -export async function CancelReadableStream(stream: any): Promise { - if (stream === null || typeof stream !== 'object') return; - - if (stream[Symbol.asyncIterator]) { - await stream[Symbol.asyncIterator]().return?.(); - return; - } - - const reader = stream.getReader(); - const cancelPromise = reader.cancel(); - reader.releaseLock(); - await cancelPromise; -} diff --git a/src/internal/to-file.ts b/src/internal/to-file.ts deleted file mode 100644 index 245e849..0000000 --- a/src/internal/to-file.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads'; -import type { FilePropertyBag } from './builtin-types'; -import { checkFileSupport } from './uploads'; - -type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView; - -/** - * Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc. - * Don't add arrayBuffer here, node-fetch doesn't have it - */ -interface BlobLike { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ - readonly size: number; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ - readonly type: string; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ - text(): Promise; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ - slice(start?: number, end?: number): BlobLike; -} - -/** - * This check adds the arrayBuffer() method type because it is available and used at runtime - */ -const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => - value != null && - typeof value === 'object' && - typeof value.size === 'number' && - typeof value.type === 'string' && - typeof value.text === 'function' && - typeof value.slice === 'function' && - typeof value.arrayBuffer === 'function'; - -/** - * Intended to match DOM File, node:buffer File, undici File, etc. - */ -interface FileLike extends BlobLike { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ - readonly lastModified: number; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ - readonly name?: string | undefined; -} - -/** - * This check adds the arrayBuffer() method type because it is available and used at runtime - */ -const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise } => - value != null && - typeof value === 'object' && - typeof value.name === 'string' && - typeof value.lastModified === 'number' && - isBlobLike(value); - -/** - * Intended to match DOM Response, node-fetch Response, undici Response, etc. - */ -export interface ResponseLike { - url: string; - blob(): Promise; -} - -const isResponseLike = (value: any): value is ResponseLike => - value != null && - typeof value === 'object' && - typeof value.url === 'string' && - typeof value.blob === 'function'; - -export type ToFileInput = - | FileLike - | ResponseLike - | Exclude - | AsyncIterable; - -/** - * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats - * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s - * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible - * @param {Object=} options additional properties - * @param {string=} options.type the MIME type of the content - * @param {number=} options.lastModified the last modified timestamp - * @returns a {@link File} with the given properties - */ -export async function toFile( - value: ToFileInput | PromiseLike, - name?: string | null | undefined, - options?: FilePropertyBag | undefined, -): Promise { - checkFileSupport(); - - // If it's a promise, resolve it. - value = await value; - - // If we've been given a `File` we don't need to do anything - if (isFileLike(value)) { - if (value instanceof File) { - return value; - } - return makeFile([await value.arrayBuffer()], value.name); - } - - if (isResponseLike(value)) { - const blob = await value.blob(); - name ||= new URL(value.url).pathname.split(/[\\/]/).pop(); - - return makeFile(await getBytes(blob), name, options); - } - - const parts = await getBytes(value); - - name ||= getName(value); - - if (!options?.type) { - const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type); - if (typeof type === 'string') { - options = { ...options, type }; - } - } - - return makeFile(parts, name, options); -} - -async function getBytes(value: BlobLikePart | AsyncIterable): Promise> { - let parts: Array = []; - if ( - typeof value === 'string' || - ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. - value instanceof ArrayBuffer - ) { - parts.push(value); - } else if (isBlobLike(value)) { - parts.push(value instanceof Blob ? value : await value.arrayBuffer()); - } else if ( - isAsyncIterable(value) // includes Readable, ReadableStream, etc. - ) { - for await (const chunk of value) { - parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating? - } - } else { - const constructor = value?.constructor?.name; - throw new Error( - `Unexpected data type: ${typeof value}${ - constructor ? `; constructor: ${constructor}` : '' - }${propsForError(value)}`, - ); - } - - return parts; -} - -function propsForError(value: unknown): string { - if (typeof value !== 'object' || value === null) return ''; - const props = Object.getOwnPropertyNames(value); - return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`; -} diff --git a/src/internal/types.ts b/src/internal/types.ts deleted file mode 100644 index b668dfc..0000000 --- a/src/internal/types.ts +++ /dev/null @@ -1,95 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export type PromiseOrValue = T | Promise; -export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; - -export type KeysEnum = { [P in keyof Required]: true }; - -export type FinalizedRequestInit = RequestInit & { headers: Headers }; - -type NotAny = [0] extends [1 & T] ? never : T; - -/** - * Some environments overload the global fetch function, and Parameters only gets the last signature. - */ -type OverloadedParameters = - T extends ( - { - (...args: infer A): unknown; - (...args: infer B): unknown; - (...args: infer C): unknown; - (...args: infer D): unknown; - } - ) ? - A | B | C | D - : T extends ( - { - (...args: infer A): unknown; - (...args: infer B): unknown; - (...args: infer C): unknown; - } - ) ? - A | B | C - : T extends ( - { - (...args: infer A): unknown; - (...args: infer B): unknown; - } - ) ? - A | B - : T extends (...args: infer A) => unknown ? A - : never; - -/* eslint-disable */ -/** - * These imports attempt to get types from a parent package's dependencies. - * Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which - * would cause typescript to show types not present at runtime. To avoid this, we import - * directly from parent node_modules folders. - * - * We need to check multiple levels because we don't know what directory structure we'll be in. - * For example, pnpm generates directories like this: - * ``` - * node_modules - * ├── .pnpm - * │ └── pkg@1.0.0 - * │ └── node_modules - * │ └── pkg - * │ └── internal - * │ └── types.d.ts - * ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg - * └── undici - * ``` - * - * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition - */ -/** @ts-ignore For users with \@types/node */ -type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users with undici */ -type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users with \@types/bun */ -type BunRequestInit = globalThis.FetchRequestInit; -/** @ts-ignore For users with node-fetch@2 */ -type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ -type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users who use Deno */ -type FetchRequestInit = NonNullable[1]>; -/* eslint-enable */ - -type RequestInits = - | NotAny - | NotAny - | NotAny - | NotAny - | NotAny - | NotAny - | NotAny; - -/** - * This type contains `RequestInit` options that may be available on the current runtime, - * including per-platform extensions like `dispatcher`, `agent`, `client`, etc. - */ -export type MergedRequestInit = RequestInits & - /** We don't include these in the types as they'll be overridden for every request. */ - Partial>; diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts deleted file mode 100644 index 486806b..0000000 --- a/src/internal/uploads.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { type RequestOptions } from './request-options'; -import type { FilePropertyBag, Fetch } from './builtin-types'; -import type { BrowserUse } from '../client'; -import { ReadableStreamFrom } from './shims'; - -export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView; -type FsReadStream = AsyncIterable & { path: string | { toString(): string } }; - -// https://github.com/oven-sh/bun/issues/5980 -interface BunFile extends Blob { - readonly name?: string | undefined; -} - -export const checkFileSupport = () => { - if (typeof File === 'undefined') { - const { process } = globalThis as any; - const isOldNode = - typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20; - throw new Error( - '`File` is not defined as a global, which is required for file uploads.' + - (isOldNode ? - " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`." - : ''), - ); - } -}; - -/** - * Typically, this is a native "File" class. - * - * We provide the {@link toFile} utility to convert a variety of objects - * into the File class. - * - * For convenience, you can also pass a fetch Response, or in Node, - * the result of fs.createReadStream(). - */ -export type Uploadable = File | Response | FsReadStream | BunFile; - -/** - * Construct a `File` instance. This is used to ensure a helpful error is thrown - * for environments that don't define a global `File` yet. - */ -export function makeFile( - fileBits: BlobPart[], - fileName: string | undefined, - options?: FilePropertyBag, -): File { - checkFileSupport(); - return new File(fileBits as any, fileName ?? 'unknown_file', options); -} - -export function getName(value: any): string | undefined { - return ( - ( - (typeof value === 'object' && - value !== null && - (('name' in value && value.name && String(value.name)) || - ('url' in value && value.url && String(value.url)) || - ('filename' in value && value.filename && String(value.filename)) || - ('path' in value && value.path && String(value.path)))) || - '' - ) - .split(/[\\/]/) - .pop() || undefined - ); -} - -export const isAsyncIterable = (value: any): value is AsyncIterable => - value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; - -/** - * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. - * Otherwise returns the request as is. - */ -export const maybeMultipartFormRequestOptions = async ( - opts: RequestOptions, - fetch: BrowserUse | Fetch, -): Promise => { - if (!hasUploadableValue(opts.body)) return opts; - - return { ...opts, body: await createForm(opts.body, fetch) }; -}; - -type MultipartFormRequestOptions = Omit & { body: unknown }; - -export const multipartFormRequestOptions = async ( - opts: MultipartFormRequestOptions, - fetch: BrowserUse | Fetch, -): Promise => { - return { ...opts, body: await createForm(opts.body, fetch) }; -}; - -const supportsFormDataMap = /* @__PURE__ */ new WeakMap>(); - -/** - * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending - * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]". - * This function detects if the fetch function provided supports the global FormData object to avoid - * confusing error messages later on. - */ -function supportsFormData(fetchObject: BrowserUse | Fetch): Promise { - const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch; - const cached = supportsFormDataMap.get(fetch); - if (cached) return cached; - const promise = (async () => { - try { - const FetchResponse = ( - 'Response' in fetch ? - fetch.Response - : (await fetch('data:,')).constructor) as typeof Response; - const data = new FormData(); - if (data.toString() === (await new FetchResponse(data).text())) { - return false; - } - return true; - } catch { - // avoid false negatives - return true; - } - })(); - supportsFormDataMap.set(fetch, promise); - return promise; -} - -export const createForm = async >( - body: T | undefined, - fetch: BrowserUse | Fetch, -): Promise => { - if (!(await supportsFormData(fetch))) { - throw new TypeError( - 'The provided fetch function does not support file uploads with the current global FormData class.', - ); - } - const form = new FormData(); - await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); - return form; -}; - -// We check for Blob not File because Bun.File doesn't inherit from File, -// but they both inherit from Blob and have a `name` property at runtime. -const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value; - -const isUploadable = (value: unknown) => - typeof value === 'object' && - value !== null && - (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value)); - -const hasUploadableValue = (value: unknown): boolean => { - if (isUploadable(value)) return true; - if (Array.isArray(value)) return value.some(hasUploadableValue); - if (value && typeof value === 'object') { - for (const k in value) { - if (hasUploadableValue((value as any)[k])) return true; - } - } - return false; -}; - -const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { - if (value === undefined) return; - if (value == null) { - throw new TypeError( - `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, - ); - } - - // TODO: make nested formats configurable - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - form.append(key, String(value)); - } else if (value instanceof Response) { - form.append(key, makeFile([await value.blob()], getName(value))); - } else if (isAsyncIterable(value)) { - form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value))); - } else if (isNamedBlob(value)) { - form.append(key, value, getName(value)); - } else if (Array.isArray(value)) { - await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); - } else if (typeof value === 'object') { - await Promise.all( - Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), - ); - } else { - throw new TypeError( - `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, - ); - } -}; diff --git a/src/internal/utils.ts b/src/internal/utils.ts deleted file mode 100644 index 3cbfacc..0000000 --- a/src/internal/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export * from './utils/values'; -export * from './utils/base64'; -export * from './utils/env'; -export * from './utils/log'; -export * from './utils/uuid'; -export * from './utils/sleep'; diff --git a/src/internal/utils/base64.ts b/src/internal/utils/base64.ts deleted file mode 100644 index 24d24f5..0000000 --- a/src/internal/utils/base64.ts +++ /dev/null @@ -1,40 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { BrowserUseError } from '../../core/error'; -import { encodeUTF8 } from './bytes'; - -export const toBase64 = (data: string | Uint8Array | null | undefined): string => { - if (!data) return ''; - - if (typeof (globalThis as any).Buffer !== 'undefined') { - return (globalThis as any).Buffer.from(data).toString('base64'); - } - - if (typeof data === 'string') { - data = encodeUTF8(data); - } - - if (typeof btoa !== 'undefined') { - return btoa(String.fromCharCode.apply(null, data as any)); - } - - throw new BrowserUseError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); -}; - -export const fromBase64 = (str: string): Uint8Array => { - if (typeof (globalThis as any).Buffer !== 'undefined') { - const buf = (globalThis as any).Buffer.from(str, 'base64'); - return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); - } - - if (typeof atob !== 'undefined') { - const bstr = atob(str); - const buf = new Uint8Array(bstr.length); - for (let i = 0; i < bstr.length; i++) { - buf[i] = bstr.charCodeAt(i); - } - return buf; - } - - throw new BrowserUseError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); -}; diff --git a/src/internal/utils/bytes.ts b/src/internal/utils/bytes.ts deleted file mode 100644 index 8da627a..0000000 --- a/src/internal/utils/bytes.ts +++ /dev/null @@ -1,32 +0,0 @@ -export function concatBytes(buffers: Uint8Array[]): Uint8Array { - let length = 0; - for (const buffer of buffers) { - length += buffer.length; - } - const output = new Uint8Array(length); - let index = 0; - for (const buffer of buffers) { - output.set(buffer, index); - index += buffer.length; - } - - return output; -} - -let encodeUTF8_: (str: string) => Uint8Array; -export function encodeUTF8(str: string) { - let encoder; - return ( - encodeUTF8_ ?? - ((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder))) - )(str); -} - -let decodeUTF8_: (bytes: Uint8Array) => string; -export function decodeUTF8(bytes: Uint8Array) { - let decoder; - return ( - decodeUTF8_ ?? - ((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder))) - )(bytes); -} diff --git a/src/internal/utils/env.ts b/src/internal/utils/env.ts deleted file mode 100644 index 2d84800..0000000 --- a/src/internal/utils/env.ts +++ /dev/null @@ -1,18 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * Read an environment variable. - * - * Trims beginning and trailing whitespace. - * - * Will return undefined if the environment variable doesn't exist or cannot be accessed. - */ -export const readEnv = (env: string): string | undefined => { - if (typeof (globalThis as any).process !== 'undefined') { - return (globalThis as any).process.env?.[env]?.trim() ?? undefined; - } - if (typeof (globalThis as any).Deno !== 'undefined') { - return (globalThis as any).Deno.env?.get?.(env)?.trim(); - } - return undefined; -}; diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts deleted file mode 100644 index 960f462..0000000 --- a/src/internal/utils/log.ts +++ /dev/null @@ -1,127 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { hasOwn } from './values'; -import { type BrowserUse } from '../../client'; -import { RequestOptions } from '../request-options'; - -type LogFn = (message: string, ...rest: unknown[]) => void; -export type Logger = { - error: LogFn; - warn: LogFn; - info: LogFn; - debug: LogFn; -}; -export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug'; - -const levelNumbers = { - off: 0, - error: 200, - warn: 300, - info: 400, - debug: 500, -}; - -export const parseLogLevel = ( - maybeLevel: string | undefined, - sourceName: string, - client: BrowserUse, -): LogLevel | undefined => { - if (!maybeLevel) { - return undefined; - } - if (hasOwn(levelNumbers, maybeLevel)) { - return maybeLevel; - } - loggerFor(client).warn( - `${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify( - Object.keys(levelNumbers), - )}`, - ); - return undefined; -}; - -function noop() {} - -function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) { - if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) { - return noop; - } else { - // Don't wrap logger functions, we want the stacktrace intact! - return logger[fnLevel].bind(logger); - } -} - -const noopLogger = { - error: noop, - warn: noop, - info: noop, - debug: noop, -}; - -let cachedLoggers = /* @__PURE__ */ new WeakMap(); - -export function loggerFor(client: BrowserUse): Logger { - const logger = client.logger; - const logLevel = client.logLevel ?? 'off'; - if (!logger) { - return noopLogger; - } - - const cachedLogger = cachedLoggers.get(logger); - if (cachedLogger && cachedLogger[0] === logLevel) { - return cachedLogger[1]; - } - - const levelLogger = { - error: makeLogFn('error', logger, logLevel), - warn: makeLogFn('warn', logger, logLevel), - info: makeLogFn('info', logger, logLevel), - debug: makeLogFn('debug', logger, logLevel), - }; - - cachedLoggers.set(logger, [logLevel, levelLogger]); - - return levelLogger; -} - -export const formatRequestDetails = (details: { - options?: RequestOptions | undefined; - headers?: Headers | Record | undefined; - retryOfRequestLogID?: string | undefined; - retryOf?: string | undefined; - url?: string | undefined; - status?: number | undefined; - method?: string | undefined; - durationMs?: number | undefined; - message?: unknown; - body?: unknown; -}) => { - if (details.options) { - details.options = { ...details.options }; - delete details.options['headers']; // redundant + leaks internals - } - if (details.headers) { - details.headers = Object.fromEntries( - (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map( - ([name, value]) => [ - name, - ( - name.toLowerCase() === 'x-browser-use-api-key' || - name.toLowerCase() === 'authorization' || - name.toLowerCase() === 'cookie' || - name.toLowerCase() === 'set-cookie' - ) ? - '***' - : value, - ], - ), - ); - } - if ('retryOfRequestLogID' in details) { - if (details.retryOfRequestLogID) { - details.retryOf = details.retryOfRequestLogID; - } - delete details.retryOfRequestLogID; - } - return details; -}; diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts deleted file mode 100644 index e194daf..0000000 --- a/src/internal/utils/path.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { BrowserUseError } from '../../core/error'; - -/** - * Percent-encode everything that isn't safe to have in a path without encoding safe chars. - * - * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3: - * > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" - * > pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - */ -export function encodeURIPath(str: string) { - return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent); -} - -const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null)); - -export const createPathTagFunction = (pathEncoder = encodeURIPath) => - function path(statics: readonly string[], ...params: readonly unknown[]): string { - // If there are no params, no processing is needed. - if (statics.length === 1) return statics[0]!; - - let postPath = false; - const invalidSegments = []; - const path = statics.reduce((previousValue, currentValue, index) => { - if (/[?#]/.test(currentValue)) { - postPath = true; - } - const value = params[index]; - let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value); - if ( - index !== params.length && - (value == null || - (typeof value === 'object' && - // handle values from other realms - value.toString === - Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY) - ?.toString)) - ) { - encoded = value + ''; - invalidSegments.push({ - start: previousValue.length + currentValue.length, - length: encoded.length, - error: `Value of type ${Object.prototype.toString - .call(value) - .slice(8, -1)} is not a valid path parameter`, - }); - } - return previousValue + currentValue + (index === params.length ? '' : encoded); - }, ''); - - const pathOnly = path.split(/[?#]/, 1)[0]!; - const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi; - let match; - - // Find all invalid segments - while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) { - invalidSegments.push({ - start: match.index, - length: match[0].length, - error: `Value "${match[0]}" can\'t be safely passed as a path parameter`, - }); - } - - invalidSegments.sort((a, b) => a.start - b.start); - - if (invalidSegments.length > 0) { - let lastEnd = 0; - const underline = invalidSegments.reduce((acc, segment) => { - const spaces = ' '.repeat(segment.start - lastEnd); - const arrows = '^'.repeat(segment.length); - lastEnd = segment.start + segment.length; - return acc + spaces + arrows; - }, ''); - - throw new BrowserUseError( - `Path parameters result in path with invalid segments:\n${invalidSegments - .map((e) => e.error) - .join('\n')}\n${path}\n${underline}`, - ); - } - - return path; - }; - -/** - * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. - */ -export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/src/internal/utils/sleep.ts b/src/internal/utils/sleep.ts deleted file mode 100644 index 65e5296..0000000 --- a/src/internal/utils/sleep.ts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/internal/utils/uuid.ts b/src/internal/utils/uuid.ts deleted file mode 100644 index b0e53aa..0000000 --- a/src/internal/utils/uuid.ts +++ /dev/null @@ -1,17 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * https://stackoverflow.com/a/2117523 - */ -export let uuid4 = function () { - const { crypto } = globalThis as any; - if (crypto?.randomUUID) { - uuid4 = crypto.randomUUID.bind(crypto); - return crypto.randomUUID(); - } - const u8 = new Uint8Array(1); - const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff; - return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => - (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16), - ); -}; diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts deleted file mode 100644 index ebd3105..0000000 --- a/src/internal/utils/values.ts +++ /dev/null @@ -1,105 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { BrowserUseError } from '../../core/error'; - -// https://url.spec.whatwg.org/#url-scheme-string -const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; - -export const isAbsoluteURL = (url: string): boolean => { - return startsWithSchemeRegexp.test(url); -}; - -export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); -export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; - -/** Returns an object if the given value isn't an object, otherwise returns as-is */ -export function maybeObj(x: unknown): object { - if (typeof x !== 'object') { - return {}; - } - - return x ?? {}; -} - -// https://stackoverflow.com/a/34491287 -export function isEmptyObj(obj: Object | null | undefined): boolean { - if (!obj) return true; - for (const _k in obj) return false; - return true; -} - -// https://eslint.org/docs/latest/rules/no-prototype-builtins -export function hasOwn(obj: T, key: PropertyKey): key is keyof T { - return Object.prototype.hasOwnProperty.call(obj, key); -} - -export function isObj(obj: unknown): obj is Record { - return obj != null && typeof obj === 'object' && !Array.isArray(obj); -} - -export const ensurePresent = (value: T | null | undefined): T => { - if (value == null) { - throw new BrowserUseError(`Expected a value to be given but received ${value} instead.`); - } - - return value; -}; - -export const validatePositiveInteger = (name: string, n: unknown): number => { - if (typeof n !== 'number' || !Number.isInteger(n)) { - throw new BrowserUseError(`${name} must be an integer`); - } - if (n < 0) { - throw new BrowserUseError(`${name} must be a positive integer`); - } - return n; -}; - -export const coerceInteger = (value: unknown): number => { - if (typeof value === 'number') return Math.round(value); - if (typeof value === 'string') return parseInt(value, 10); - - throw new BrowserUseError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceFloat = (value: unknown): number => { - if (typeof value === 'number') return value; - if (typeof value === 'string') return parseFloat(value); - - throw new BrowserUseError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceBoolean = (value: unknown): boolean => { - if (typeof value === 'boolean') return value; - if (typeof value === 'string') return value === 'true'; - return Boolean(value); -}; - -export const maybeCoerceInteger = (value: unknown): number | undefined => { - if (value === undefined) { - return undefined; - } - return coerceInteger(value); -}; - -export const maybeCoerceFloat = (value: unknown): number | undefined => { - if (value === undefined) { - return undefined; - } - return coerceFloat(value); -}; - -export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { - if (value === undefined) { - return undefined; - } - return coerceBoolean(value); -}; - -export const safeJSON = (text: string) => { - try { - return JSON.parse(text); - } catch (err) { - return undefined; - } -}; diff --git a/src/lib/.keep b/src/lib/.keep deleted file mode 100644 index 7554f8b..0000000 --- a/src/lib/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store custom files to expand the SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/src/lib/bin/auth.ts b/src/lib/bin/auth.ts deleted file mode 100644 index 278723e..0000000 --- a/src/lib/bin/auth.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as dotenv from '@dotenvx/dotenvx'; - -import { BrowserUse } from '../../'; - -const API_KEY_ENV_VAR_KEY = 'BROWSER_USE_API_KEY'; - -/** - * Creates a new BrowserUse client with the API key from the environment variable. - */ -export function createBrowserUseClient() { - let apiKey: string | null = null; - - if (process.env[API_KEY_ENV_VAR_KEY]) { - apiKey = process.env[API_KEY_ENV_VAR_KEY]; - } - - if (apiKey == null) { - const env = dotenv.config({ path: '.env' }); - - const envApiKey = env.parsed?.[API_KEY_ENV_VAR_KEY]; - - if (envApiKey) { - apiKey = envApiKey; - } - } - - if (apiKey == null) { - console.error(`Missing ${API_KEY_ENV_VAR_KEY} environment variable!`); - process.exit(1); - } - - return new BrowserUse({ apiKey }); -} - -const SECRET_ENV_VAR_KEY = 'SECRET_KEY'; - -/** - * Loads the Browser Use webhook secret from the environment variable. - */ -export function getBrowserUseWebhookSecret() { - let secret: string | null = null; - - if (process.env[SECRET_ENV_VAR_KEY]) { - secret = process.env[SECRET_ENV_VAR_KEY]; - } - - if (secret == null) { - const env = dotenv.config({ path: '.env' }); - - const envSecret = env.parsed?.[SECRET_ENV_VAR_KEY]; - - if (envSecret) { - secret = envSecret; - } - } - - if (secret == null) { - console.error(`Missing ${SECRET_ENV_VAR_KEY} environment variable!`); - process.exit(1); - } - - return secret; -} diff --git a/src/lib/bin/cli.ts b/src/lib/bin/cli.ts deleted file mode 100755 index e254c3b..0000000 --- a/src/lib/bin/cli.ts +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node - -import { program } from 'commander'; -import { listen } from './commands/listen'; - -program - .name('browser-use') - .description('CLI to some JavaScript string utilities') - .version('0.8.0') - .addCommand(listen) - .parse(process.argv); diff --git a/src/lib/bin/commands/listen.ts b/src/lib/bin/commands/listen.ts deleted file mode 100644 index 54de8cb..0000000 --- a/src/lib/bin/commands/listen.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Command } from 'commander'; - -import { APIUserAbortError, BrowserUse } from '../../../'; -import { createWebhookSignature, Webhook } from '../../webhooks'; -import { createBrowserUseClient, getBrowserUseWebhookSecret } from '../auth'; - -// NOTE: We perform task list refresh to get all running tasks and then -const tickRef: { - timeout: NodeJS.Timeout | null; - abort: AbortController | null; -} = { timeout: null, abort: null }; - -export const listen = new Command('listen') - .description(`Open a local webhook to receive Cloud API updates from the CLI on your local machine.`) - .argument('', 'The endpoint to forward updates to.') - .action(async (endpoint) => { - // Auth - - const client = createBrowserUseClient(); - const secret = getBrowserUseWebhookSecret(); - - // Proxy - - const localTargetEndpoint = endpoint; - - if (typeof localTargetEndpoint !== 'string') { - // NOTE: This should never happen because the command is validated by commander. - throw new Error( - 'Something unexpected happened. Please report this issue. https://github.com/browser-use/browser-use-node/issues', - ); - } - - const localTargetURL = new URL(localTargetEndpoint); - - // - - const startTimeDate = new Date(); - const queue: { current: Webhook[] } = { current: [] }; - const runs: Map = new Map(); - - tickRef.timeout = setInterval(async () => { - // NOTE: On next tick, we abort the current abort controller. - if (tickRef.abort != null) { - tickRef.abort.abort(); - } - - const controller = new AbortController(); - - tickRef.abort = controller; - - console.log(`[polling] ${new Date().toISOString()} `.padEnd(100, '=')); - - const tasks: BrowserUse.Tasks.TaskItemView[] = await client.tasks - .list( - { - pageSize: 10, - // NOTE: There's a bug in the API where the datetime needs to be provided in naive format.cur - after: startTimeDate.toISOString().replace('Z', ''), - }, - { - signal: tickRef.abort.signal, - }, - ) - .then((res) => res.items) - .catch((err) => { - if (err instanceof APIUserAbortError) { - return []; - } - - console.log(`[polling] ${new Date().toISOString()} failed`); - console.error(err); - - return []; - }); - - for (const task of tasks) { - const currentTaskStatus = runs.get(task.id); - - const timestamp = task.finishedAt ? task.finishedAt : task.startedAt; - - if (currentTaskStatus == null) { - // NOTE: The task is new and the CLI hasn't yet captured it in the current run. - queue.current.push({ - type: 'agent.task.status_update', - timestamp, - payload: { - session_id: task.sessionId, - task_id: task.id, - status: task.status, - metadata: task.metadata, - }, - }); - - runs.set(task.id, task.status); - - continue; - } else { - // NOTE: CLI has registered the task in the registry and we need to compare. - if (task.status !== currentTaskStatus) { - queue.current.push({ - type: 'agent.task.status_update', - timestamp, - payload: { - session_id: task.sessionId, - task_id: task.id, - status: task.status, - metadata: task.metadata, - }, - }); - - runs.set(task.id, task.status); - - continue; - } - } - } - - // Send Events - - const events: (Webhook & { internal?: true })[] = [ - // NOTE: We push the ping request on every tick to ensure the webhook is alive. - { - type: 'test', - timestamp: new Date().toISOString(), - payload: { test: 'ok' }, - internal: true, - }, - ...queue.current, - ]; - - const promises = events.map(async (update) => { - const body = JSON.stringify(update); - - const signature = createWebhookSignature({ - payload: update.payload, - timestamp: update.timestamp, - secret, - }); - - try { - const res = await fetch(localTargetURL, { - method: 'POST', - body, - headers: { - 'Content-Type': 'application/json', - // https://docs.browser-use.com/cloud/webhooks#implementing-webhook-verification - 'X-Browser-Use-Timestamp': update.timestamp, - 'X-Browser-Use-Signature': signature, - }, - - signal: controller.signal, - }); - - console.log(`[update] ${update.timestamp} ${update.type} ${res.status}`); - - return { delivery: 'fulfilled', update, status: res.status }; - } catch (err) { - console.log(`[update] ${update.timestamp} ${update.type} failed`); - - return { delivery: 'rejected', update, error: err }; - } - }); - - const delivery = await Promise.all(promises); - - // NOTE: We preserve the rejected updates so we can retry them. - queue.current = delivery - .filter((d) => d.delivery === 'rejected' && d.update.internal !== true) - .map((d) => d.update); - }, 1_000); - - console.log(`Forwarding updates to: ${localTargetEndpoint}!`); - }); - -process.on('SIGINT', () => { - if (tickRef.abort != null) { - tickRef.abort.abort(); - } - - if (tickRef.timeout) { - clearInterval(tickRef.timeout); - } -}); diff --git a/src/lib/nextjs/hooks/useBrowserUse.ts b/src/lib/nextjs/hooks/useBrowserUse.ts deleted file mode 100644 index 1f9ab84..0000000 --- a/src/lib/nextjs/hooks/useBrowserUse.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useEffect, useState } from 'react'; -import type { ZodType } from 'zod'; - -import type { BrowserUseEvent } from '../server/utils'; -import { TaskViewWithSchema } from '../../parse'; - -/** - * A hook to stream Browser Use updates to the client. - */ -export function useBrowserUse(route: string): TaskViewWithSchema | null { - const [status, setStatus] = useState | null>(null); - - useEffect(() => { - const es = new EventSource(route); - - es.addEventListener('status', (e) => { - if (e instanceof MessageEvent) { - const msg = JSON.parse(e.data) as BrowserUseEvent; - - setStatus(msg.data); - - if (msg.data.status === 'finished') { - es.close(); - } - } else { - console.error('Event is not a MessageEvent', e); - } - }); - - es.addEventListener('end', () => es.close()); - es.addEventListener('error', () => es.close()); - - return () => es.close(); - }, [route]); - - return status; -} diff --git a/src/lib/nextjs/server/utils.ts b/src/lib/nextjs/server/utils.ts deleted file mode 100644 index 6c8959c..0000000 --- a/src/lib/nextjs/server/utils.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { TaskViewWithSchema } from '../../parse'; -import { ZodType } from 'zod'; - -export type BrowserUseEvent = { - event: 'status'; - data: TaskViewWithSchema; -}; - -/** - * Convert an async generator to a stream. - * - * @param gen - The async generator to convert to a stream. - * @returns A stream of the async generator. - */ -export function gtos( - gen: AsyncGenerator<{ - event: 'status'; - data: TaskViewWithSchema; - }>, - opts?: { - /** - * Called when an event is emitted. - */ - onEvent?: (event: TaskViewWithSchema) => void; - - /** - * Called when the task is finished. - */ - onFinished?: (event: TaskViewWithSchema) => void; - }, -): ReadableStream> { - const enc = new TextEncoder(); - - const stream = new ReadableStream({ - async start(controller) { - // open the SSE stream quickly - controller.enqueue(enc.encode(': connected\n\n')); - - try { - for await (const msg of gen) { - opts?.onEvent?.(msg.data); - - const data: BrowserUseEvent = { - event: msg.event, - data: msg.data, - }; - - const encoded = JSON.stringify(data); - - const payload = `event: ${msg.event}\ndata: ${encoded}\n\n`; - - controller.enqueue(enc.encode(payload)); - - if (msg.data.status === 'finished') { - opts?.onFinished?.(msg.data); - } - } - - controller.enqueue(enc.encode('event: end\ndata: {}\n\n')); - } catch (e) { - controller.enqueue(enc.encode(`event: error\ndata: ${JSON.stringify({ message: String(e) })}\n\n`)); - } finally { - controller.close(); - } - }, - }); - - return stream; -} diff --git a/src/lib/parse.ts b/src/lib/parse.ts deleted file mode 100644 index b01e92d..0000000 --- a/src/lib/parse.ts +++ /dev/null @@ -1,46 +0,0 @@ -import z, { type ZodType } from 'zod'; -import type { TaskCreateParams, TaskView } from '../resources/tasks'; - -// RUN - -export type TaskCreateParamsWithSchema = Omit & { - schema: T; -}; - -export function stringifyStructuredOutput(schema: T): string { - return JSON.stringify(z.toJSONSchema(schema)); -} - -// RETRIEVE - -export type TaskViewWithSchema = TaskView & { - parsedOutput: z.output | null; -}; - -export function parseStructuredTaskOutput( - res: TaskView, - schema: T, -): TaskViewWithSchema { - if (res.doneOutput == null) { - return { ...res, parsedOutput: null }; - } - - try { - const parsed = JSON.parse(res.doneOutput); - - const response = schema.safeParse(parsed); - if (!response.success) { - throw new Error(`Invalid structured output: ${response.error.message}`); - } - - return { ...res, parsedOutput: response.data }; - } catch (e) { - if (e instanceof SyntaxError) { - return { - ...res, - parsedOutput: null, - }; - } - throw e; - } -} diff --git a/src/lib/stream.ts b/src/lib/stream.ts deleted file mode 100644 index 89f747a..0000000 --- a/src/lib/stream.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createHash } from 'crypto'; -import stringify from 'fast-json-stable-stringify'; - -import type { TaskView } from '../resources'; - -/** - * Hashes the task view to detect changes. - * Uses fast-json-stable-stringify for deterministic JSON, then SHA-256. - */ -export function getTaskViewHash(view: TaskView): string { - const dump = stringify(view); - const hash = createHash('sha256').update(dump).digest('hex'); - return hash; -} diff --git a/src/lib/types.ts b/src/lib/types.ts deleted file mode 100644 index a1b9bda..0000000 --- a/src/lib/types.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Distributive Pick - does not collapse unions into a "shared type" only to - * run Pick on it. Instead, it "picks" from each union item separately. - * - * See https://github.com/klimashkin/css-modules-theme/pull/8 - * - * Example: - * Pick<{ type: "pick" } | { type: "omit" }, "type"> - * produces { type: "pick" | "omit" } - * - * UnionPick<{ type: "pick" } | { type: "omit" }, "type"> - * produces { type: "pick" } | { type: "omit" } - */ -export type UnionPick = T extends unknown ? Pick : never; - -/** - * Like UnionPick, but for Omit - */ -export type UnionOmit = T extends unknown ? Omit : never; - -/** - * Utility type for properties that may be undefined until loaded. - */ -export type Loadable = ({ loading: true } & { [K in keyof T]?: never }) | ({ loading: false } & T); - -/** - * Utility type that removes null fields from a type. - */ -export type DeepRequired = { - [P in keyof T]: Exclude; -}; - -/** - * Makes a type check that is only valid when all cases of a switch - * statement have been convered. - */ -export class ExhaustiveSwitchCheck extends Error { - constructor(val: never) { - super(`Unreachable case: ${JSON.stringify(val)}`); - } -} - -/** - * A utiliy type that lets you extract a union member by its `kind` property. - * - * @example - * - * type Shape = - * | { kind: 'circle'; radius: number } - * | { kind: 'square'; sideLength: number } - * | { kind: 'rectangle'; width: number; height: number }; - * - * type Circle = ExtractKind; // { kind: 'circle'; radius: number } - */ -export type ExtractKind = T extends { kind: K } ? T : never; - -/** - * A utiliy type that lets you extract a union member by its `ok` property. - * - * @example - * - * type Result = { ok: true; value: string } | { ok: false; error: string }; - * - * type Ok = ExtractResult; // { ok: true; value: string } - */ -export type ExtractResult = T extends { ok: K } ? T : never; - -/** - * Creates a deep readonly object mutable. - */ -export type DeepMutable = { - -readonly [P in keyof T]: T[P] extends object ? DeepMutable : T[P]; -}; diff --git a/src/lib/webhooks.ts b/src/lib/webhooks.ts deleted file mode 100644 index 8c181e3..0000000 --- a/src/lib/webhooks.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { createHmac } from 'crypto'; -import { z } from 'zod'; -import stringify from 'fast-json-stable-stringify'; - -// https://docs.browser-use.com/cloud/webhooks - -// - -export const zWebhookTimestamp = z.iso.datetime({ offset: true, local: true }); - -// test - -export const zWebhookTestPayload = z.object({ - test: z.literal('ok'), -}); - -export type WebhookTestPayload = z.infer; - -export const zWebhookTest = z.object({ - type: z.literal('test'), - timestamp: zWebhookTimestamp, - payload: zWebhookTestPayload, -}); - -// agent.task.status_update - -export const zWebhookAgentTaskStatusUpdatePayloadMetadata = z.record(z.string(), z.unknown()).optional(); - -export const zWebhookAgentTaskStatusUpdatePayloadStatus = z.literal([ - 'initializing', - 'started', - 'paused', - 'stopped', - 'finished', -]); - -export const zWebhookAgentTaskStatusUpdatePayload = z.object({ - session_id: z.string(), - task_id: z.string(), - status: zWebhookAgentTaskStatusUpdatePayloadStatus, - metadata: zWebhookAgentTaskStatusUpdatePayloadMetadata, -}); - -export type WebhookAgentTaskStatusUpdatePayload = z.infer; - -export const zWebhookAgentTaskStatusUpdate = z.object({ - type: z.literal('agent.task.status_update'), - timestamp: zWebhookTimestamp, - payload: zWebhookAgentTaskStatusUpdatePayload, -}); - -// - -export const zWebhookSchema = z.discriminatedUnion('type', [ - // - zWebhookTest, - zWebhookAgentTaskStatusUpdate, -]); - -export type Webhook = z.infer; - -// Signature - -/** - * Utility function that validates the received Webhook event/ - */ -export async function verifyWebhookEventSignature( - evt: { - body: string | object; - signature: string; - timestamp: string; - }, - cfg: { secret: string }, -): Promise<{ ok: true; event: Webhook } | { ok: false }> { - try { - const json = typeof evt.body === 'string' ? JSON.parse(evt.body) : evt.body; - const event = await zWebhookSchema.safeParseAsync(json); - - if (event.success === false) { - return { ok: false }; - } - - const signature = createWebhookSignature({ - payload: event.data.payload, - timestamp: evt.timestamp, - secret: cfg.secret, - }); - - // Compare signatures using timing-safe comparison - if (evt.signature !== signature) { - return { ok: false }; - } - - return { ok: true, event: event.data }; - } catch (err) { - console.error(err); - return { ok: false }; - } -} - -/** - * Creates a webhook signature for the given payload, timestamp, and secret. - */ -export function createWebhookSignature({ - payload, - timestamp, - secret, -}: { - payload: unknown; - timestamp: string; - secret: string; -}): string { - const dump = stringify(payload); - const message = `${timestamp}.${dump}`; - - const hmac = createHmac('sha256', secret); - hmac.update(message); - return hmac.digest('hex'); -} diff --git a/src/resource.ts b/src/resource.ts deleted file mode 100644 index 363e351..0000000 --- a/src/resource.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/resource instead */ -export * from './core/resource'; diff --git a/src/resources.ts b/src/resources.ts deleted file mode 100644 index b283d57..0000000 --- a/src/resources.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './resources/index'; diff --git a/src/resources/agent-profiles.ts b/src/resources/agent-profiles.ts deleted file mode 100644 index be769d8..0000000 --- a/src/resources/agent-profiles.ts +++ /dev/null @@ -1,245 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { buildHeaders } from '../internal/headers'; -import { RequestOptions } from '../internal/request-options'; -import { path } from '../internal/utils/path'; - -export class AgentProfiles extends APIResource { - /** - * Create a new agent profile for the authenticated user. - * - * Agent profiles define how your AI agents behave during tasks. You can create - * multiple profiles for different use cases (e.g., customer support, data - * analysis, web scraping). Free users can create 1 profile; paid users can create - * unlimited profiles. - * - * Key features you can configure: - * - * - System prompt: The core instructions that define the agent's personality and - * behavior - * - Allowed domains: Restrict which websites the agent can access - * - Max steps: Limit how many actions the agent can take in a single task - * - Vision: Enable/disable the agent's ability to see and analyze screenshots - * - Thinking: Enable/disable the agent's reasoning process - * - * Args: - * - * - request: The agent profile configuration including name, description, and - * behavior settings - * - * Returns: - * - * - The newly created agent profile with all its details - * - * Raises: - * - * - 402: If user needs a subscription to create additional profiles - */ - create(body: AgentProfileCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/agent-profiles', { body, ...options }); - } - - /** - * Get a specific agent profile by its ID. - * - * Retrieves the complete details of an agent profile, including all its - * configuration settings like system prompts, allowed domains, and behavior flags. - * - * Args: - * - * - profile_id: The unique identifier of the agent profile - * - * Returns: - * - * - Complete agent profile information - * - * Raises: - * - * - 404: If the user agent profile doesn't exist - */ - retrieve(profileID: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/agent-profiles/${profileID}`, options); - } - - /** - * Update an existing agent profile. - * - * Modify any aspect of an agent profile, such as its name, description, system - * prompt, or behavior settings. Only the fields you provide will be updated; other - * fields remain unchanged. - * - * Args: - * - * - profile_id: The unique identifier of the agent profile to update - * - request: The fields to update (only provided fields will be changed) - * - * Returns: - * - * - The updated agent profile with all its current details - * - * Raises: - * - * - 404: If the user agent profile doesn't exist - */ - update( - profileID: string, - body: AgentProfileUpdateParams, - options?: RequestOptions, - ): APIPromise { - return this._client.patch(path`/agent-profiles/${profileID}`, { body, ...options }); - } - - /** - * Get a paginated list of all agent profiles for the authenticated user. - * - * Agent profiles define how your AI agents behave, including their personality, - * capabilities, and limitations. Use this endpoint to see all your configured - * agent profiles. - * - * Returns: - * - * - A paginated list of agent profiles - * - Total count of profiles - * - Page information for navigation - */ - list( - query: AgentProfileListParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/agent-profiles', { query, ...options }); - } - - /** - * Delete an agent profile. - * - * Permanently removes an agent profile and all its configuration. This action - * cannot be undone. Any tasks that were using this profile will continue to work, - * but you won't be able to create new tasks with the deleted profile. - * - * Args: - * - * - profile_id: The unique identifier of the agent profile to delete - * - * Returns: - * - * - 204 No Content on successful deletion (idempotent) - */ - delete(profileID: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/agent-profiles/${profileID}`, { - ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), - }); - } -} - -/** - * View model for representing an agent profile - * - * Attributes: id: Unique identifier for the profile name: Display name for the - * profile description: Optional description of the profile highlight_elements: - * Whether to highlight elements during agent interaction with the browser - * max_agent_steps: Maximum number of steps the agent can take before stopping - * allowed_domains: List of domains the agent is allowed to access flash_mode: - * Whether flash mode is enabled thinking: Whether thinking mode is enabled vision: - * Whether vision capabilities are enabled custom_system_prompt_extension: Optional - * custom system prompt for the agent created_at: Timestamp when the profile was - * created updated_at: Timestamp when the profile was last updated - */ -export interface AgentProfileView { - id: string; - - allowedDomains: Array; - - createdAt: string; - - customSystemPromptExtension: string; - - description: string; - - flashMode: boolean; - - highlightElements: boolean; - - maxAgentSteps: number; - - name: string; - - thinking: boolean; - - updatedAt: string; - - vision: boolean; -} - -/** - * Response model for paginated agent profile list requests - * - * Attributes: items: List of agent profile views for the current page - */ -export interface AgentProfileListResponse { - items: Array; - - pageNumber: number; - - pageSize: number; - - totalItems: number; -} - -export interface AgentProfileCreateParams { - name: string; - - allowedDomains?: Array; - - customSystemPromptExtension?: string; - - description?: string; - - flashMode?: boolean; - - highlightElements?: boolean; - - maxAgentSteps?: number; - - thinking?: boolean; - - vision?: boolean; -} - -export interface AgentProfileUpdateParams { - allowedDomains?: Array | null; - - customSystemPromptExtension?: string | null; - - description?: string | null; - - flashMode?: boolean | null; - - highlightElements?: boolean | null; - - maxAgentSteps?: number | null; - - name?: string | null; - - thinking?: boolean | null; - - vision?: boolean | null; -} - -export interface AgentProfileListParams { - pageNumber?: number; - - pageSize?: number; -} - -export declare namespace AgentProfiles { - export { - type AgentProfileView as AgentProfileView, - type AgentProfileListResponse as AgentProfileListResponse, - type AgentProfileCreateParams as AgentProfileCreateParams, - type AgentProfileUpdateParams as AgentProfileUpdateParams, - type AgentProfileListParams as AgentProfileListParams, - }; -} diff --git a/src/resources/browser-profiles.ts b/src/resources/browser-profiles.ts deleted file mode 100644 index d68f169..0000000 --- a/src/resources/browser-profiles.ts +++ /dev/null @@ -1,255 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { buildHeaders } from '../internal/headers'; -import { RequestOptions } from '../internal/request-options'; -import { path } from '../internal/utils/path'; - -export class BrowserProfiles extends APIResource { - /** - * Create a new browser profile for the authenticated user. - * - * Browser profiles define how your web browsers behave during AI agent tasks. You - * can create multiple profiles for different use cases (e.g., mobile testing, - * desktop browsing, proxy-enabled scraping). Free users can create up to 10 - * profiles; paid users can create unlimited profiles. - * - * Key features you can configure: - * - * - Viewport dimensions: Set the browser window size for consistent rendering - * - Mobile emulation: Enable mobile device simulation - * - Proxy settings: Route traffic through specific locations or proxy servers - * - Ad blocking: Enable/disable ad blocking for cleaner browsing - * - Cache persistence: Choose whether to save browser data between sessions - * - * Args: - * - * - request: The browser profile configuration including name, description, and - * browser settings - * - * Returns: - * - * - The newly created browser profile with all its details - * - * Raises: - * - * - 402: If user needs a subscription to create additional profiles - */ - create(body: BrowserProfileCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/browser-profiles', { body, ...options }); - } - - /** - * Get a specific browser profile by its ID. - * - * Retrieves the complete details of a browser profile, including all its - * configuration settings like viewport dimensions, proxy settings, and behavior - * flags. - * - * Args: - * - * - profile_id: The unique identifier of the browser profile - * - * Returns: - * - * - Complete browser profile information - * - * Raises: - * - * - 404: If the user browser profile doesn't exist - */ - retrieve(profileID: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/browser-profiles/${profileID}`, options); - } - - /** - * Update an existing browser profile. - * - * Modify any aspect of a browser profile, such as its name, description, viewport - * settings, or proxy configuration. Only the fields you provide will be updated; - * other fields remain unchanged. - * - * Args: - * - * - profile_id: The unique identifier of the browser profile to update - * - request: The fields to update (only provided fields will be changed) - * - * Returns: - * - * - The updated browser profile with all its current details - * - * Raises: - * - * - 404: If the user browser profile doesn't exist - */ - update( - profileID: string, - body: BrowserProfileUpdateParams, - options?: RequestOptions, - ): APIPromise { - return this._client.patch(path`/browser-profiles/${profileID}`, { body, ...options }); - } - - /** - * Get a paginated list of all browser profiles for the authenticated user. - * - * Browser profiles define how your web browsers behave during AI agent tasks, - * including settings like viewport size, mobile emulation, proxy configuration, - * and ad blocking. Use this endpoint to see all your configured browser profiles. - * - * Returns: - * - * - A paginated list of browser profiles - * - Total count of profiles - * - Page information for navigation - */ - list( - query: BrowserProfileListParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/browser-profiles', { query, ...options }); - } - - /** - * Delete a browser profile. - * - * Permanently removes a browser profile and all its configuration. This action - * cannot be undone. The profile will also be removed from the browser service. Any - * active sessions using this profile will continue to work, but you won't be able - * to create new sessions with the deleted profile. - * - * Args: - * - * - profile_id: The unique identifier of the browser profile to delete - * - * Returns: - * - * - 204 No Content on successful deletion (idempotent) - */ - delete(profileID: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/browser-profiles/${profileID}`, { - ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), - }); - } -} - -/** - * View model for representing a browser profile - * - * Attributes: id: Unique identifier for the profile name: Display name for the - * profile description: Optional description of the profile persist: Whether - * browser state should persist between sessions ad_blocker: Whether ad blocking is - * enabled proxy: Whether proxy is enabled proxy_country_code: Country code for - * proxy location store_cache: Whether to store browser cache - * browser_viewport_width: Browser viewport width in pixels - * browser_viewport_height: Browser viewport height in pixels is_mobile: Whether - * the browser should be in mobile view created_at: Timestamp when the profile was - * created updated_at: Timestamp when the profile was last updated - */ -export interface BrowserProfileView { - id: string; - - adBlocker: boolean; - - browserViewportHeight: number; - - browserViewportWidth: number; - - createdAt: string; - - description: string; - - isMobile: boolean; - - name: string; - - persist: boolean; - - proxy: boolean; - - proxyCountryCode: ProxyCountryCode; - - storeCache: boolean; - - updatedAt: string; -} - -export type ProxyCountryCode = 'us' | 'uk' | 'fr' | 'it' | 'jp' | 'au' | 'de' | 'fi' | 'ca' | 'in'; - -/** - * Response model for paginated browser profile list requests - * - * Attributes: items: List of browser profile views for the current page - */ -export interface BrowserProfileListResponse { - items: Array; - - pageNumber: number; - - pageSize: number; - - totalItems: number; -} - -export interface BrowserProfileCreateParams { - name: string; - - adBlocker?: boolean; - - browserViewportHeight?: number; - - browserViewportWidth?: number; - - description?: string; - - isMobile?: boolean; - - persist?: boolean; - - proxy?: boolean; - - proxyCountryCode?: ProxyCountryCode; - - storeCache?: boolean; -} - -export interface BrowserProfileUpdateParams { - adBlocker?: boolean | null; - - browserViewportHeight?: number | null; - - browserViewportWidth?: number | null; - - description?: string | null; - - isMobile?: boolean | null; - - name?: string | null; - - persist?: boolean | null; - - proxy?: boolean | null; - - proxyCountryCode?: ProxyCountryCode | null; - - storeCache?: boolean | null; -} - -export interface BrowserProfileListParams { - pageNumber?: number; - - pageSize?: number; -} - -export declare namespace BrowserProfiles { - export { - type BrowserProfileView as BrowserProfileView, - type ProxyCountryCode as ProxyCountryCode, - type BrowserProfileListResponse as BrowserProfileListResponse, - type BrowserProfileCreateParams as BrowserProfileCreateParams, - type BrowserProfileUpdateParams as BrowserProfileUpdateParams, - type BrowserProfileListParams as BrowserProfileListParams, - }; -} diff --git a/src/resources/index.ts b/src/resources/index.ts deleted file mode 100644 index 6061ea2..0000000 --- a/src/resources/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { - AgentProfiles, - type AgentProfileView, - type AgentProfileListResponse, - type AgentProfileCreateParams, - type AgentProfileUpdateParams, - type AgentProfileListParams, -} from './agent-profiles'; -export { - BrowserProfiles, - type BrowserProfileView, - type ProxyCountryCode, - type BrowserProfileListResponse, - type BrowserProfileCreateParams, - type BrowserProfileUpdateParams, - type BrowserProfileListParams, -} from './browser-profiles'; -export { - Sessions, - type SessionStatus, - type SessionView, - type SessionListResponse, - type SessionUpdateParams, - type SessionListParams, -} from './sessions/sessions'; -export { - Tasks, - type FileView, - type TaskItemView, - type TaskStatus, - type TaskStepView, - type TaskView, - type TaskCreateResponse, - type TaskListResponse, - type TaskGetLogsResponse, - type TaskGetOutputFileResponse, - type TaskGetUserUploadedFileResponse, - type TaskCreateParams, - type TaskUpdateParams, - type TaskListParams, - type TaskGetOutputFileParams, - type TaskGetUserUploadedFileParams, -} from './tasks'; -export { Users } from './users/users'; diff --git a/src/resources/sessions.ts b/src/resources/sessions.ts deleted file mode 100644 index 253b6db..0000000 --- a/src/resources/sessions.ts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export * from './sessions/index'; diff --git a/src/resources/sessions/index.ts b/src/resources/sessions/index.ts deleted file mode 100644 index f13cf66..0000000 --- a/src/resources/sessions/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { PublicShare, type ShareView } from './public-share'; -export { - Sessions, - type SessionStatus, - type SessionView, - type SessionListResponse, - type SessionUpdateParams, - type SessionListParams, -} from './sessions'; diff --git a/src/resources/sessions/public-share.ts b/src/resources/sessions/public-share.ts deleted file mode 100644 index 9de5001..0000000 --- a/src/resources/sessions/public-share.ts +++ /dev/null @@ -1,109 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import { APIPromise } from '../../core/api-promise'; -import { buildHeaders } from '../../internal/headers'; -import { RequestOptions } from '../../internal/request-options'; -import { path } from '../../internal/utils/path'; - -export class PublicShare extends APIResource { - /** - * Create a public share for a session. - * - * Generates a public sharing link that allows anyone with the URL to view the - * session and its tasks. If a public share already exists for the session, it will - * return the existing share instead of creating a new one. - * - * Public shares are useful for: - * - * - Sharing results with clients or team members - * - Demonstrating AI agent capabilities - * - Collaborative review of automated tasks - * - * Args: - * - * - session_id: The unique identifier of the agent session to share - * - * Returns: - * - * - Public share information including the share URL and usage statistics - * - * Raises: - * - * - 404: If the user agent session doesn't exist - */ - create(sessionID: string, options?: RequestOptions): APIPromise { - return this._client.post(path`/sessions/${sessionID}/public-share`, options); - } - - /** - * Get information about the public share for a session. - * - * Retrieves details about the public sharing link for a session, including the - * share token, public URL, view count, and last viewed timestamp. This is useful - * for monitoring how your shared sessions are being accessed. - * - * Args: - * - * - session_id: The unique identifier of the agent session - * - * Returns: - * - * - Public share information including the share URL and usage statistics - * - * Raises: - * - * - 404: If the user agent session doesn't exist or doesn't have a public share - */ - retrieve(sessionID: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/sessions/${sessionID}/public-share`, options); - } - - /** - * Remove the public share for a session. - * - * Deletes the public sharing link for a session, making it no longer accessible to - * anyone with the previous share URL. This is useful for removing access to - * sensitive sessions or when you no longer want to share the results. - * - * Args: - * - * - session_id: The unique identifier of the agent session - * - * Returns: - * - * - 204 No Content on successful deletion (idempotent) - * - * Raises: - * - * - 404: If the user agent session doesn't exist - */ - delete(sessionID: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/sessions/${sessionID}/public-share`, { - ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), - }); - } -} - -/** - * View model for representing a public share of a session. - * - * Attributes: share_token: Token to access the public share. share_url: URL to - * access the public share. view_count: Number of times the public share has been - * viewed. last_viewed_at: Timestamp of the last time the public share was viewed - * (None if never viewed). - */ -export interface ShareView { - shareToken: string; - - shareUrl: string; - - viewCount: number; - - lastViewedAt?: string | null; -} - -export declare namespace PublicShare { - export { type ShareView as ShareView }; -} diff --git a/src/resources/sessions/sessions.ts b/src/resources/sessions/sessions.ts deleted file mode 100644 index 0942244..0000000 --- a/src/resources/sessions/sessions.ts +++ /dev/null @@ -1,240 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import * as SessionsAPI from './sessions'; -import * as TasksAPI from '../tasks'; -import * as PublicShareAPI from './public-share'; -import { PublicShare, ShareView } from './public-share'; -import { APIPromise } from '../../core/api-promise'; -import { buildHeaders } from '../../internal/headers'; -import { RequestOptions } from '../../internal/request-options'; -import { path } from '../../internal/utils/path'; - -export class Sessions extends APIResource { - publicShare: PublicShareAPI.PublicShare = new PublicShareAPI.PublicShare(this._client); - - /** - * Get detailed information about a specific AI agent session. - * - * Retrieves comprehensive information about a session, including its current - * status, live browser URL (if active), recording URL (if completed), and optional - * task details. This endpoint is useful for monitoring active sessions or - * reviewing completed ones. - * - * Args: - * - * - session_id: The unique identifier of the agent session - * - params: Optional parameters to control what data is included - * - * Returns: - * - * - Complete session information including status, URLs, and optional task details - * - * Raises: - * - * - 404: If the user agent session doesn't exist - */ - retrieve(sessionID: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/sessions/${sessionID}`, options); - } - - /** - * Update a session's status or perform actions on it. - * - * Currently supports stopping a session, which will: - * - * 1. Stop any running tasks in the session - * 2. End the browser session - * 3. Generate a recording URL if available - * 4. Update the session status to 'stopped' - * - * This is useful for manually stopping long-running sessions or when you want to - * end a session before all tasks are complete. - * - * Args: - * - * - session_id: The unique identifier of the agent session to update - * - request: The action to perform on the session - * - * Returns: - * - * - The updated session information including the new status and recording URL - * - * Raises: - * - * - 404: If the user agent session doesn't exist - */ - update(sessionID: string, body: SessionUpdateParams, options?: RequestOptions): APIPromise { - return this._client.patch(path`/sessions/${sessionID}`, { body, ...options }); - } - - /** - * Get a paginated list of all AI agent sessions for the authenticated user. - * - * AI agent sessions represent active or completed browsing sessions where your AI - * agents perform tasks. Each session can contain multiple tasks and maintains - * browser state throughout the session lifecycle. - * - * You can filter sessions by status and optionally include task details for each - * session. - * - * Returns: - * - * - A paginated list of agent sessions - * - Total count of sessions - * - Page information for navigation - * - Optional task details for each session (if requested) - */ - list( - query: SessionListParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/sessions', { query, ...options }); - } - - /** - * Delete a session and all its associated data. - * - * Permanently removes a session and all its tasks, browser data, and public - * shares. This action cannot be undone. Use this endpoint to clean up old sessions - * and free up storage space. - * - * Args: - * - * - session_id: The unique identifier of the agent session to delete - * - * Returns: - * - * - 204 No Content on successful deletion (idempotent) - */ - delete(sessionID: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/sessions/${sessionID}`, { - ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), - }); - } -} - -/** - * Enumeration of possible (browser) session states - * - * Attributes: ACTIVE: Session is currently active and running (browser is running) - * STOPPED: Session has been stopped and is no longer active (browser is stopped) - */ -export type SessionStatus = 'active' | 'stopped'; - -/** - * View model for representing a (browser) session with its associated tasks. - * - * Attributes: id: Unique identifier for the session. status: Current status of the - * session (active/stopped). live_url: URL where the browser can be viewed live in - * real-time. started_at: Timestamp when the session was created and started. - * finished_at: Timestamp when the session was stopped (None if still active). - * tasks: Optional list of tasks associated with this session. record_url: URL to - * access the recorded session playback. public_share_url: Optional URL to access - * the public share of the session. - */ -export interface SessionView { - id: string; - - startedAt: string; - - /** - * Enumeration of possible (browser) session states - * - * Attributes: ACTIVE: Session is currently active and running (browser is running) - * STOPPED: Session has been stopped and is no longer active (browser is stopped) - */ - status: SessionStatus; - - finishedAt?: string | null; - - liveUrl?: string | null; - - publicShareUrl?: string | null; - - recordUrl?: string | null; - - tasks?: Array | null; -} - -/** - * Response model for paginated session list requests - * - * Attributes: items: List of session views for the current page - */ -export interface SessionListResponse { - items: Array; - - pageNumber: number; - - pageSize: number; - - totalItems: number; -} - -export namespace SessionListResponse { - /** - * View model for representing a (browser) session with its associated tasks. - * - * Attributes: id: Unique identifier for the session. status: Current status of the - * session (active/stopped). live_url: URL where the browser can be viewed live in - * real-time. started_at: Timestamp when the session was created and started. - * finished_at: Timestamp when the session was stopped (None if still active). - */ - export interface Item { - id: string; - - startedAt: string; - - /** - * Enumeration of possible (browser) session states - * - * Attributes: ACTIVE: Session is currently active and running (browser is running) - * STOPPED: Session has been stopped and is no longer active (browser is stopped) - */ - status: SessionsAPI.SessionStatus; - - finishedAt?: string | null; - - liveUrl?: string | null; - } -} - -export interface SessionUpdateParams { - /** - * Available actions that can be performed on a session - * - * Attributes: STOP: Stop the session and all its associated tasks (cannot be - * undone) - */ - action: 'stop'; -} - -export interface SessionListParams { - /** - * Enumeration of possible (browser) session states - * - * Attributes: ACTIVE: Session is currently active and running (browser is running) - * STOPPED: Session has been stopped and is no longer active (browser is stopped) - */ - filterBy?: SessionStatus | null; - - pageNumber?: number; - - pageSize?: number; -} - -Sessions.PublicShare = PublicShare; - -export declare namespace Sessions { - export { - type SessionStatus as SessionStatus, - type SessionView as SessionView, - type SessionListResponse as SessionListResponse, - type SessionUpdateParams as SessionUpdateParams, - type SessionListParams as SessionListParams, - }; - - export { PublicShare as PublicShare, type ShareView as ShareView }; -} diff --git a/src/resources/tasks.ts b/src/resources/tasks.ts deleted file mode 100644 index 0060bdf..0000000 --- a/src/resources/tasks.ts +++ /dev/null @@ -1,783 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { ZodType } from 'zod'; - -import { APIPromise } from '../core/api-promise'; -import { APIResource } from '../core/resource'; -import { RequestOptions } from '../internal/request-options'; -import { path } from '../internal/utils/path'; -import { - parseStructuredTaskOutput, - stringifyStructuredOutput, - type TaskCreateParamsWithSchema, - type TaskViewWithSchema, -} from '../lib/parse'; -import { getTaskViewHash } from '../lib/stream'; -import { ExhaustiveSwitchCheck } from '../lib/types'; - -export class Tasks extends APIResource { - /** - * Create and start a new Browser Use Agent task. - * - * This is the main endpoint for running AI agents. You can either: - * - * 1. Start a new session with a new task. - * 2. Add a follow-up task to an existing session. - * - * When starting a new session: - * - * - A new browser session is created - * - Credits are deducted from your account - * - The agent begins executing your task immediately - * - * When adding to an existing session: - * - * - The agent continues in the same browser context - * - No additional browser start up costs are charged (browser session is already - * active) - * - The agent can build on previous work - * - * Key features: - * - * - Agent profiles: Define agent behavior and capabilities - * - Browser profiles: Control browser settings and environment (only used for new - * sessions) - * - File uploads: Include documents for the agent to work with - * - Structured output: Define the format of the task result - * - Task metadata: Add custom data for tracking and organization - * - * Args: - * - * - request: Complete task configuration including agent settings, browser - * settings, and task description - * - * Returns: - * - * - The created task ID together with the task's session ID - * - * Raises: - * - * - 402: If user has insufficient credits for a new session - * - 404: If referenced agent/browser profiles don't exist - * - 400: If session is stopped or already has a running task - */ - create( - body: TaskCreateParamsWithSchema, - options?: RequestOptions, - ): APIPromise; - create(body: TaskCreateParams, options?: RequestOptions): APIPromise; - create( - body: TaskCreateParams | TaskCreateParamsWithSchema, - options?: RequestOptions, - ): APIPromise { - if ('schema' in body && body.schema != null && typeof body.schema === 'object') { - const schema = body.schema; - - const _body: TaskCreateParams = { - ...body, - structuredOutputJson: stringifyStructuredOutput(schema), - }; - - return this._client.post('/tasks', { body: _body, ...options }); - } - - return this._client.post('/tasks', { body, ...options }); - } - - /** - * Get detailed information about a specific AI agent task. - * - * Retrieves comprehensive information about a task, including its current status, - * progress, and detailed execution data. You can choose to get just the status - * (for quick polling) or full details including steps and file information. - * - * Use this endpoint to: - * - * - Monitor task progress in real-time - * - Review completed task results - * - Debug failed tasks by examining steps - * - Download output files and logs - * - * Args: - * - * - task_id: The unique identifier of the agent task - * - * Returns: - * - * - Complete task information - * - * Raises: - * - * - 404: If the user agent task doesn't exist - */ - retrieve( - req: { taskId: string; schema: T }, - options?: RequestOptions, - ): APIPromise>; - retrieve(taskID: string, options?: RequestOptions): APIPromise; - retrieve(req: string | { taskId: string; schema: ZodType }, options?: RequestOptions): APIPromise { - if (typeof req === 'string') { - return this._client.get(path`/tasks/${req}`, options); - } - - const { taskId, schema } = req; - - return this._client - .get(path`/tasks/${taskId}`, options) - ._thenUnwrap((rsp) => parseStructuredTaskOutput(rsp as TaskView, schema)); - } - - private async *watch( - taskId: string, - config: { interval: number }, - options?: RequestOptions, - ): AsyncGenerator<{ event: 'status'; data: TaskView }> { - const hash: { current: string | null } = { current: null }; - - poll: do { - if (options?.signal?.aborted) { - break poll; - } - - const res = await this.retrieve(taskId); - - const resHash = getTaskViewHash(res); - - if (hash.current == null || resHash !== hash.current) { - hash.current = resHash; - - yield { event: 'status', data: res }; - } - - switch (res.status) { - case 'finished': - case 'stopped': - case 'paused': - break poll; - case 'started': - await new Promise((resolve) => setTimeout(resolve, config.interval)); - break; - default: - throw new ExhaustiveSwitchCheck(res.status); - } - } while (true); - } - - stream( - body: { - taskId: string; - schema: T; - }, - options?: RequestOptions, - ): AsyncGenerator<{ event: 'status'; data: TaskViewWithSchema }>; - stream(taskId: string, options?: RequestOptions): AsyncGenerator<{ event: 'status'; data: TaskView }>; - async *stream( - body: string | { taskId: string; schema: ZodType }, - options?: RequestOptions, - ): AsyncGenerator { - const taskId = typeof body === 'object' ? body.taskId : body; - - for await (const msg of this.watch(taskId, { interval: 500 }, options)) { - if (options?.signal?.aborted) { - break; - } - - if (typeof body === 'object') { - const parsed = parseStructuredTaskOutput(msg.data, body.schema); - yield { event: 'status', data: parsed }; - } else { - yield { event: 'status', data: msg.data }; - } - } - } - - /** - * Create and run an agent task. - * - * @returns The output of the task. - */ - run( - body: TaskCreateParamsWithSchema, - options?: RequestOptions, - ): APIPromise>; - run(body: TaskCreateParams, options?: RequestOptions): APIPromise; - run( - body: TaskCreateParams | TaskCreateParamsWithSchema, - options?: RequestOptions, - ): APIPromise { - if ('schema' in body && body.schema != null && typeof body.schema === 'object') { - return this.create(body, options)._thenUnwrap(async (data) => { - const taskId = data.id; - - for await (const msg of this.stream({ taskId, schema: body.schema }, options)) { - if (msg.data.status === 'finished') { - return msg.data; - } - } - - throw new Error('Task did not finish'); - }); - } - - return this.create(body, options)._thenUnwrap(async (data) => { - const taskId = data.id; - - for await (const msg of this.stream(taskId, options)) { - if (msg.data.status === 'finished') { - return msg.data; - } - } - - throw new Error('Task did not finish'); - }); - } - - /** - * Control the execution of an AI agent task. - * - * Allows you to pause, resume, or stop tasks, and optionally stop the entire - * session. This is useful for: - * - * - Pausing long-running tasks to review progress - * - Stopping tasks that are taking too long - * - Ending sessions when you're done with all tasks - * - * Available actions: - * - * - STOP: Stop the current task - * - PAUSE: Pause the task (can be resumed later) - * - RESUME: Resume a paused task - * - STOP_TASK_AND_SESSION: Stop the task and end the entire session - * - * Args: - * - * - task_id: The unique identifier of the agent task to control - * - request: The action to perform on the task - * - * Returns: - * - * - The updated task information - * - * Raises: - * - * - 404: If the user agent task doesn't exist - */ - update(taskID: string, body: TaskUpdateParams, options?: RequestOptions): APIPromise { - return this._client.patch(path`/tasks/${taskID}`, { body, ...options }); - } - - /** - * Get a paginated list of all Browser Use Agent tasks for the authenticated user. - * - * Browser Use Agent tasks are the individual jobs that your agents perform within - * a session. Each task represents a specific instruction or goal that the agent - * works on, such as filling out a form, extracting data, or navigating to specific - * pages. - * - * Returns: - * - * - A paginated list of Browser Use Agent tasks - * - Total count of Browser Use Agent tasks - * - Page information for navigation - */ - list( - query: TaskListParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/tasks', { query, ...options }); - } - - /** - * Get a download URL for the execution logs of an AI agent task. - * - * Task logs contain detailed information about how the AI agent executed the task, - * including: - * - * - Step-by-step reasoning and decisions - * - Actions taken on web pages - * - Error messages and debugging information - * - Performance metrics and timing data - * - * This is useful for: - * - * - Understanding how the agent solved the task - * - Debugging failed or unexpected results - * - Optimizing agent behavior and prompts - * - Auditing agent actions for compliance - * - * Args: - * - * - task_id: The unique identifier of the agent task - * - * Returns: - * - * - A presigned download URL for the task log file - * - * Raises: - * - * - 404: If the user agent task doesn't exist - * - 500: If the download URL cannot be generated (should not happen) - */ - getLogs(taskID: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/tasks/${taskID}/logs`, options); - } - - /** - * Get a download URL for a specific output file generated by an AI agent task. - * - * AI agents can generate various output files during task execution, such as: - * - * - Screenshots of web pages - * - Extracted data in CSV/JSON format - * - Generated reports or documents - * - Downloaded files from websites - * - * This endpoint provides a secure, time-limited download URL for accessing these - * files. The URL expires after a short time for security. - * - * Args: - * - * - task_id: The unique identifier of the agent task - * - file_id: The unique identifier of the output file - * - * Returns: - * - * - A presigned download URL for the requested file - * - * Raises: - * - * - 404: If the user agent task or output file doesn't exist - * - 500: If the download URL cannot be generated (should not happen) - */ - getOutputFile( - fileID: string, - params: TaskGetOutputFileParams, - options?: RequestOptions, - ): APIPromise { - const { task_id } = params; - return this._client.get(path`/tasks/${task_id}/output-files/${fileID}`, options); - } - - /** - * Get a download URL for a specific user uploaded file that was used in the task. - * - * A user can upload files to their account file bucket and reference the name of - * the file in a task. These files are then made available for the agent to use - * during the agent task run. - * - * This endpoint provides a secure, time-limited download URL for accessing these - * files. The URL expires after a short time for security. - * - * Args: - * - * - task_id: The unique identifier of the agent task - * - file_id: The unique identifier of the user uploaded file - * - * Returns: - * - * - A presigned download URL for the requested file - * - * Raises: - * - * - 404: If the user agent task or user uploaded file doesn't exist - * - 500: If the download URL cannot be generated (should not happen) - */ - getUserUploadedFile( - fileID: string, - params: TaskGetUserUploadedFileParams, - options?: RequestOptions, - ): APIPromise { - const { task_id } = params; - return this._client.get(path`/tasks/${task_id}/user-uploaded-files/${fileID}`, options); - } -} - -/** - * View model for representing an output file generated by the agent - * - * Attributes: id: Unique identifier for the output file file_name: Name of the - * output file - */ -export interface FileView { - id: string; - - fileName: string; -} - -/** - * View model for representing a task with its execution details - * - * Attributes: id: Unique identifier for the task session_id: ID of the session - * this task belongs to llm: The LLM model used for this task represented as a - * string task: The task prompt/instruction given to the agent status: Current - * status of the task execution started_at: Naive UTC timestamp when the task was - * started finished_at: Naive UTC timestamp when the task completed (None if still - * running) metadata: Optional additional metadata associated with the task set by - * the user is_scheduled: Whether this task was created as a scheduled task steps: - * Optional list of execution steps done_output: Final output/result of the task - * user_uploaded_files: Optional list of files uploaded by user for this task - * output_files: Optional list of files generated as output by this task - * browser_use_version: Version of browser-use used for this task (older tasks may - * not have this set) is_success: Whether the task was successful (self-reported by - * the agent) - */ -export interface TaskItemView { - id: string; - - isScheduled: boolean; - - llm: string; - - sessionId: string; - - startedAt: string; - - /** - * Enumeration of possible task execution states - * - * Attributes: STARTED: Task has been started and is currently running. PAUSED: - * Task execution has been temporarily paused (can be resumed) FINISHED: Task has - * finished and the agent has completed the task. STOPPED: Task execution has been - * manually stopped (cannot be resumed). - */ - status: TaskStatus; - - task: string; - - browserUseVersion?: string | null; - - doneOutput?: string | null; - - finishedAt?: string | null; - - isSuccess?: boolean | null; - - metadata?: { [key: string]: unknown }; -} - -/** - * Enumeration of possible task execution states - * - * Attributes: STARTED: Task has been started and is currently running. PAUSED: - * Task execution has been temporarily paused (can be resumed) FINISHED: Task has - * finished and the agent has completed the task. STOPPED: Task execution has been - * manually stopped (cannot be resumed). - */ -export type TaskStatus = 'started' | 'paused' | 'finished' | 'stopped'; - -/** - * View model for representing a single step in a task's execution - * - * Attributes: number: Sequential step number within the task memory: Agent's - * memory at this step evaluation_previous_goal: Agent's evaluation of the previous - * goal completion next_goal: The goal for the next step url: Current URL the - * browser is on for this step screenshot_url: Optional URL to the screenshot taken - * at this step actions: List of stringified json actions performed by the agent in - * this step - */ -export interface TaskStepView { - actions: Array; - - evaluationPreviousGoal: string; - - memory: string; - - nextGoal: string; - - number: number; - - url: string; - - screenshotUrl?: string | null; -} - -/** - * View model for representing a task with its execution details - * - * Attributes: id: Unique identifier for the task session_id: ID of the session - * this task belongs to session: The session this task belongs to llm: The LLM - * model used for this task represented as a string task: The task - * prompt/instruction given to the agent status: Current status of the task - * execution started_at: Naive UTC timestamp when the task was started finished_at: - * Naive UTC timestamp when the task completed (None if still running) metadata: - * Optional additional metadata associated with the task set by the user - * is_scheduled: Whether this task was created as a scheduled task steps: List of - * execution steps done_output: Final output/result of the task - * user_uploaded_files: List of files uploaded by user for this task output_files: - * List of files generated as output by this task browser_use_version: Version of - * browser-use used for this task (older tasks may not have this set) is_success: - * Whether the task was successful (self-reported by the agent) - */ -export interface TaskView { - id: string; - - isScheduled: boolean; - - llm: string; - - outputFiles: Array; - - /** - * View model for representing a session that a task belongs to - * - * Attributes: id: Unique identifier for the session status: Current status of the - * session (active/stopped) live_url: URL where the browser can be viewed live in - * real-time. started_at: Timestamp when the session was created and started. - * finished_at: Timestamp when the session was stopped (None if still active). - */ - session: TaskView.Session; - - sessionId: string; - - startedAt: string; - - /** - * Enumeration of possible task execution states - * - * Attributes: STARTED: Task has been started and is currently running. PAUSED: - * Task execution has been temporarily paused (can be resumed) FINISHED: Task has - * finished and the agent has completed the task. STOPPED: Task execution has been - * manually stopped (cannot be resumed). - */ - status: TaskStatus; - - steps: Array; - - task: string; - - userUploadedFiles: Array; - - browserUseVersion?: string | null; - - doneOutput?: string | null; - - finishedAt?: string | null; - - isSuccess?: boolean | null; - - metadata?: { [key: string]: unknown }; -} - -export namespace TaskView { - /** - * View model for representing a session that a task belongs to - * - * Attributes: id: Unique identifier for the session status: Current status of the - * session (active/stopped) live_url: URL where the browser can be viewed live in - * real-time. started_at: Timestamp when the session was created and started. - * finished_at: Timestamp when the session was stopped (None if still active). - */ - export interface Session { - id: string; - - startedAt: string; - - /** - * Enumeration of possible (browser) session states - * - * Attributes: ACTIVE: Session is currently active and running (browser is running) - * STOPPED: Session has been stopped and is no longer active (browser is stopped) - */ - status: 'active' | 'stopped'; - - finishedAt?: string | null; - - liveUrl?: string | null; - } -} - -/** - * Response model for creating a task - * - * Attributes: task_id: An unique identifier for the created task session_id: The - * ID of the session this task belongs to - */ -export interface TaskCreateResponse { - id: string; - - sessionId: string; -} - -/** - * Response model for paginated task list requests - * - * Attributes: items: List of task views for the current page - */ -export interface TaskListResponse { - items: Array; - - pageNumber: number; - - pageSize: number; - - totalItems: number; -} - -/** - * Response model for log file requests - * - * Attributes: download_url: URL to download the log file - */ -export interface TaskGetLogsResponse { - downloadUrl: string; -} - -/** - * Response model for output file requests - * - * Attributes: id: Unique identifier for the output file file_name: Name of the - * output file download_url: URL to download the output file - */ -export interface TaskGetOutputFileResponse { - id: string; - - downloadUrl: string; - - fileName: string; -} - -/** - * Response model for user uploaded file requests - * - * Attributes: id: Unique identifier for the user uploaded file file_name: Name of - * the user uploaded file download_url: URL to download the user uploaded file - */ -export interface TaskGetUserUploadedFileResponse { - id: string; - - downloadUrl: string; - - fileName: string; -} - -export interface TaskCreateParams { - task: string; - - /** - * Configuration settings for the agent - * - * Attributes: llm: The LLM model to use for the agent start_url: Optional URL to - * start the agent on (will not be changed as a step) profile_id: Unique identifier - * of the agent profile to use for the task - */ - agentSettings?: TaskCreateParams.AgentSettings; - - /** - * Configuration settings for the browser session - * - * Attributes: session_id: Unique identifier of existing session to continue - * profile_id: Unique identifier of browser profile to use (use if you want to - * start a new session) - */ - browserSettings?: TaskCreateParams.BrowserSettings; - - includedFileNames?: Array | null; - - metadata?: { [key: string]: string } | null; - - secrets?: { [key: string]: string } | null; - - structuredOutputJson?: string | null; -} - -export namespace TaskCreateParams { - /** - * Configuration settings for the agent - * - * Attributes: llm: The LLM model to use for the agent start_url: Optional URL to - * start the agent on (will not be changed as a step) profile_id: Unique identifier - * of the agent profile to use for the task - */ - export interface AgentSettings { - llm?: - | 'gpt-4.1' - | 'gpt-4.1-mini' - | 'o4-mini' - | 'o3' - | 'gemini-2.5-flash' - | 'gemini-2.5-pro' - | 'claude-sonnet-4-20250514' - | 'gpt-4o' - | 'gpt-4o-mini' - | 'llama-4-maverick-17b-128e-instruct' - | 'claude-3-7-sonnet-20250219'; - - profileId?: string | null; - - startUrl?: string | null; - } - - /** - * Configuration settings for the browser session - * - * Attributes: session_id: Unique identifier of existing session to continue - * profile_id: Unique identifier of browser profile to use (use if you want to - * start a new session) - */ - export interface BrowserSettings { - profileId?: string | null; - - sessionId?: string | null; - } -} - -export interface TaskUpdateParams { - /** - * Available actions that can be performed on a task - * - * Attributes: STOP: Stop the current task execution PAUSE: Pause the current task - * execution RESUME: Resume a paused task execution STOP_TASK_AND_SESSION: Stop - * both the task and its parent session - */ - action: 'stop' | 'pause' | 'resume' | 'stop_task_and_session'; -} - -export interface TaskListParams { - after?: string | null; - - before?: string | null; - - /** - * Enumeration of possible task execution states - * - * Attributes: STARTED: Task has been started and is currently running. PAUSED: - * Task execution has been temporarily paused (can be resumed) FINISHED: Task has - * finished and the agent has completed the task. STOPPED: Task execution has been - * manually stopped (cannot be resumed). - */ - filterBy?: TaskStatus | null; - - pageNumber?: number; - - pageSize?: number; - - sessionId?: string | null; -} - -export interface TaskGetOutputFileParams { - task_id: string; -} - -export interface TaskGetUserUploadedFileParams { - task_id: string; -} - -export declare namespace Tasks { - export { - type FileView as FileView, - type TaskItemView as TaskItemView, - type TaskStatus as TaskStatus, - type TaskStepView as TaskStepView, - type TaskView as TaskView, - type TaskCreateResponse as TaskCreateResponse, - type TaskListResponse as TaskListResponse, - type TaskGetLogsResponse as TaskGetLogsResponse, - type TaskGetOutputFileResponse as TaskGetOutputFileResponse, - type TaskGetUserUploadedFileResponse as TaskGetUserUploadedFileResponse, - type TaskCreateParams as TaskCreateParams, - type TaskUpdateParams as TaskUpdateParams, - type TaskListParams as TaskListParams, - type TaskGetOutputFileParams as TaskGetOutputFileParams, - type TaskGetUserUploadedFileParams as TaskGetUserUploadedFileParams, - }; -} diff --git a/src/resources/users.ts b/src/resources/users.ts deleted file mode 100644 index db908c7..0000000 --- a/src/resources/users.ts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export * from './users/index'; diff --git a/src/resources/users/index.ts b/src/resources/users/index.ts deleted file mode 100644 index 666f33a..0000000 --- a/src/resources/users/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { Me, type MeRetrieveResponse } from './me/index'; -export { Users } from './users'; diff --git a/src/resources/users/me.ts b/src/resources/users/me.ts deleted file mode 100644 index 54b12df..0000000 --- a/src/resources/users/me.ts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export * from './me/index'; diff --git a/src/resources/users/me/files.ts b/src/resources/users/me/files.ts deleted file mode 100644 index cda7bb2..0000000 --- a/src/resources/users/me/files.ts +++ /dev/null @@ -1,95 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../../core/resource'; -import { APIPromise } from '../../../core/api-promise'; -import { RequestOptions } from '../../../internal/request-options'; - -export class Files extends APIResource { - /** - * Get a presigned URL for uploading files that AI agents can use during tasks. - * - * This endpoint generates a secure, time-limited upload URL that allows you to - * upload files directly to our storage system. These files can then be referenced - * in AI agent tasks for the agent to work with. - * - * Supported use cases: - * - * - Uploading documents for data extraction tasks - * - Providing reference materials for agents - * - Sharing files that agents need to process - * - Including images or PDFs for analysis - * - * The upload URL expires after 2 minutes for security. Files are automatically - * organized by user ID and can be referenced in task creation using the returned - * file name. - * - * Args: - * - * - request: File upload details including name, content type, and size - * - * Returns: - * - * - Presigned upload URL and form fields for direct file upload - * - * Raises: - * - * - 400: If the content type is unsupported - * - 500: If the upload URL generation fails (should not happen) - */ - createPresignedURL( - body: FileCreatePresignedURLParams, - options?: RequestOptions, - ): APIPromise { - return this._client.post('/users/me/files/presigned-url', { body, ...options }); - } -} - -/** - * Response model for a presigned upload URL - * - * Attributes: url: The URL to upload the file to method: The HTTP method to use - * for the upload fields: The form fields to include in the upload request - * file_name: The name of the file to upload (should be referenced when user wants - * to use the file in a task) expires_in: The number of seconds until the presigned - * URL expires - */ -export interface FileCreatePresignedURLResponse { - expiresIn: number; - - fields: { [key: string]: string }; - - fileName: string; - - method: 'POST'; - - url: string; -} - -export interface FileCreatePresignedURLParams { - contentType: - | 'image/jpg' - | 'image/jpeg' - | 'image/png' - | 'image/gif' - | 'image/webp' - | 'image/svg+xml' - | 'application/pdf' - | 'application/msword' - | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - | 'application/vnd.ms-excel' - | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - | 'text/plain' - | 'text/csv' - | 'text/markdown'; - - fileName: string; - - sizeBytes: number; -} - -export declare namespace Files { - export { - type FileCreatePresignedURLResponse as FileCreatePresignedURLResponse, - type FileCreatePresignedURLParams as FileCreatePresignedURLParams, - }; -} diff --git a/src/resources/users/me/index.ts b/src/resources/users/me/index.ts deleted file mode 100644 index 347401d..0000000 --- a/src/resources/users/me/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { Files, type FileCreatePresignedURLResponse, type FileCreatePresignedURLParams } from './files'; -export { Me, type MeRetrieveResponse } from './me'; diff --git a/src/resources/users/me/me.ts b/src/resources/users/me/me.ts deleted file mode 100644 index fbc4508..0000000 --- a/src/resources/users/me/me.ts +++ /dev/null @@ -1,68 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../../core/resource'; -import * as FilesAPI from './files'; -import { FileCreatePresignedURLParams, FileCreatePresignedURLResponse, Files } from './files'; -import { APIPromise } from '../../../core/api-promise'; -import { RequestOptions } from '../../../internal/request-options'; - -export class Me extends APIResource { - files: FilesAPI.Files = new FilesAPI.Files(this._client); - - /** - * Get information about the currently authenticated user. - * - * Retrieves your user profile information including: - * - * - Credit balances (monthly and additional credits in USD) - * - Account details (email, name, signup date) - * - * This endpoint is useful for: - * - * - Checking your remaining credits before running tasks - * - Displaying user information in your application - * - * Returns: - * - * - Complete user profile information including credits and account details - * - * Raises: - * - * - 404: If the user profile cannot be found - */ - retrieve(options?: RequestOptions): APIPromise { - return this._client.get('/users/me', options); - } -} - -/** - * View model for user information - * - * Attributes: monthly_credits_balance_usd: The monthly credits balance in USD - * additional_credits_balance_usd: The additional credits balance in USD email: The - * email address of the user name: The name of the user signed_up_at: The date and - * time the user signed up - */ -export interface MeRetrieveResponse { - additionalCreditsBalanceUsd: number; - - monthlyCreditsBalanceUsd: number; - - signedUpAt: string; - - email?: string | null; - - name?: string | null; -} - -Me.Files = Files; - -export declare namespace Me { - export { type MeRetrieveResponse as MeRetrieveResponse }; - - export { - Files as Files, - type FileCreatePresignedURLResponse as FileCreatePresignedURLResponse, - type FileCreatePresignedURLParams as FileCreatePresignedURLParams, - }; -} diff --git a/src/resources/users/users.ts b/src/resources/users/users.ts deleted file mode 100644 index d8cbc33..0000000 --- a/src/resources/users/users.ts +++ /dev/null @@ -1,15 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import * as MeAPI from './me/me'; -import { Me, MeRetrieveResponse } from './me/me'; - -export class Users extends APIResource { - me: MeAPI.Me = new MeAPI.Me(this._client); -} - -Users.Me = Me; - -export declare namespace Users { - export { Me as Me, type MeRetrieveResponse as MeRetrieveResponse }; -} diff --git a/src/uploads.ts b/src/uploads.ts deleted file mode 100644 index b2ef647..0000000 --- a/src/uploads.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/uploads instead */ -export * from './core/uploads'; diff --git a/src/version.ts b/src/version.ts index 54c8a47..a547456 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '1.2.0'; // x-release-please-version +export const SDK_VERSION = "0.0.28"; diff --git a/tests/BrowserTestEnvironment.ts b/tests/BrowserTestEnvironment.ts new file mode 100644 index 0000000..0f32bf7 --- /dev/null +++ b/tests/BrowserTestEnvironment.ts @@ -0,0 +1,17 @@ +import { TestEnvironment } from "jest-environment-jsdom"; + +class BrowserTestEnvironment extends TestEnvironment { + async setup() { + await super.setup(); + this.global.Request = Request; + this.global.Response = Response; + this.global.ReadableStream = ReadableStream; + this.global.TextEncoder = TextEncoder; + this.global.TextDecoder = TextDecoder; + this.global.FormData = FormData; + this.global.File = File; + this.global.Blob = Blob; + } +} + +export default BrowserTestEnvironment; diff --git a/tests/api-resources/agent-profiles.test.ts b/tests/api-resources/agent-profiles.test.ts deleted file mode 100644 index 5884295..0000000 --- a/tests/api-resources/agent-profiles.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BrowserUse from 'browser-use-sdk'; - -const client = new BrowserUse({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource agentProfiles', () => { - // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.agentProfiles.create({ name: 'x' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.agentProfiles.create({ - name: 'x', - allowedDomains: ['string'], - customSystemPromptExtension: 'x', - description: 'x', - flashMode: true, - highlightElements: true, - maxAgentSteps: 1, - thinking: true, - vision: true, - }); - }); - - // Prism tests are disabled - test.skip('retrieve', async () => { - const responsePromise = client.agentProfiles.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('update', async () => { - const responsePromise = client.agentProfiles.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', {}); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.agentProfiles.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('list: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.agentProfiles.list({ pageNumber: 1, pageSize: 1 }, { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(BrowserUse.NotFoundError); - }); - - // Prism tests are disabled - test.skip('delete', async () => { - const responsePromise = client.agentProfiles.delete('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/tests/api-resources/browser-profiles.test.ts b/tests/api-resources/browser-profiles.test.ts deleted file mode 100644 index eaa58bd..0000000 --- a/tests/api-resources/browser-profiles.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BrowserUse from 'browser-use-sdk'; - -const client = new BrowserUse({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource browserProfiles', () => { - // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.browserProfiles.create({ name: 'x' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.browserProfiles.create({ - name: 'x', - adBlocker: true, - browserViewportHeight: 100, - browserViewportWidth: 100, - description: 'x', - isMobile: true, - persist: true, - proxy: true, - proxyCountryCode: 'us', - storeCache: true, - }); - }); - - // Prism tests are disabled - test.skip('retrieve', async () => { - const responsePromise = client.browserProfiles.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('update', async () => { - const responsePromise = client.browserProfiles.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', {}); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.browserProfiles.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('list: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.browserProfiles.list({ pageNumber: 1, pageSize: 1 }, { path: '/_stainless_unknown_path' }), - ).rejects.toThrow(BrowserUse.NotFoundError); - }); - - // Prism tests are disabled - test.skip('delete', async () => { - const responsePromise = client.browserProfiles.delete('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/tests/api-resources/sessions/public-share.test.ts b/tests/api-resources/sessions/public-share.test.ts deleted file mode 100644 index 3342a93..0000000 --- a/tests/api-resources/sessions/public-share.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BrowserUse from 'browser-use-sdk'; - -const client = new BrowserUse({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource publicShare', () => { - // Prism tests are disabled - test.skip('create', async () => { - const responsePromise = client.sessions.publicShare.create('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('retrieve', async () => { - const responsePromise = client.sessions.publicShare.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('delete', async () => { - const responsePromise = client.sessions.publicShare.delete('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/tests/api-resources/sessions/sessions.test.ts b/tests/api-resources/sessions/sessions.test.ts deleted file mode 100644 index 06f77ae..0000000 --- a/tests/api-resources/sessions/sessions.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BrowserUse from 'browser-use-sdk'; - -const client = new BrowserUse({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource sessions', () => { - // Prism tests are disabled - test.skip('retrieve', async () => { - const responsePromise = client.sessions.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('update: only required params', async () => { - const responsePromise = client.sessions.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { - action: 'stop', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('update: required and optional params', async () => { - const response = await client.sessions.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { action: 'stop' }); - }); - - // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.sessions.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('list: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.sessions.list( - { filterBy: 'active', pageNumber: 1, pageSize: 1 }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(BrowserUse.NotFoundError); - }); - - // Prism tests are disabled - test.skip('delete', async () => { - const responsePromise = client.sessions.delete('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/tests/api-resources/tasks.test.ts b/tests/api-resources/tasks.test.ts deleted file mode 100644 index 720b29d..0000000 --- a/tests/api-resources/tasks.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BrowserUse from 'browser-use-sdk'; - -const client = new BrowserUse({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource tasks', () => { - // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.tasks.create({ task: 'x' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.tasks.create({ - task: 'x', - agentSettings: { - llm: 'gpt-4.1', - profileId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - startUrl: 'startUrl', - }, - browserSettings: { - profileId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - sessionId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - }, - includedFileNames: ['string'], - metadata: { foo: 'string' }, - secrets: { foo: 'string' }, - structuredOutputJson: 'structuredOutputJson', - }); - }); - - // Prism tests are disabled - test.skip('retrieve', async () => { - const responsePromise = client.tasks.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('update: only required params', async () => { - const responsePromise = client.tasks.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { action: 'stop' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('update: required and optional params', async () => { - const response = await client.tasks.update('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { action: 'stop' }); - }); - - // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.tasks.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('list: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.tasks.list( - { - after: '2019-12-27T18:11:19.117Z', - before: '2019-12-27T18:11:19.117Z', - filterBy: 'started', - pageNumber: 1, - pageSize: 1, - sessionId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(BrowserUse.NotFoundError); - }); - - // Prism tests are disabled - test.skip('getLogs', async () => { - const responsePromise = client.tasks.getLogs('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('getOutputFile: only required params', async () => { - const responsePromise = client.tasks.getOutputFile('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { - task_id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('getOutputFile: required and optional params', async () => { - const response = await client.tasks.getOutputFile('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { - task_id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - }); - }); - - // Prism tests are disabled - test.skip('getUserUploadedFile: only required params', async () => { - const responsePromise = client.tasks.getUserUploadedFile('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { - task_id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('getUserUploadedFile: required and optional params', async () => { - const response = await client.tasks.getUserUploadedFile('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', { - task_id: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - }); - }); -}); diff --git a/tests/api-resources/users/me/files.test.ts b/tests/api-resources/users/me/files.test.ts deleted file mode 100644 index 4bd615c..0000000 --- a/tests/api-resources/users/me/files.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BrowserUse from 'browser-use-sdk'; - -const client = new BrowserUse({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource files', () => { - // Prism tests are disabled - test.skip('createPresignedURL: only required params', async () => { - const responsePromise = client.users.me.files.createPresignedURL({ - contentType: 'image/jpg', - fileName: 'x', - sizeBytes: 1, - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('createPresignedURL: required and optional params', async () => { - const response = await client.users.me.files.createPresignedURL({ - contentType: 'image/jpg', - fileName: 'x', - sizeBytes: 1, - }); - }); -}); diff --git a/tests/api-resources/users/me/me.test.ts b/tests/api-resources/users/me/me.test.ts deleted file mode 100644 index f31ccda..0000000 --- a/tests/api-resources/users/me/me.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BrowserUse from 'browser-use-sdk'; - -const client = new BrowserUse({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource me', () => { - // Prism tests are disabled - test.skip('retrieve', async () => { - const responsePromise = client.users.me.retrieve(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/tests/base64.test.ts b/tests/base64.test.ts deleted file mode 100644 index 3996bd7..0000000 --- a/tests/base64.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { fromBase64, toBase64 } from 'browser-use-sdk/internal/utils/base64'; - -describe.each(['Buffer', 'atob'])('with %s', (mode) => { - let originalBuffer: BufferConstructor; - beforeAll(() => { - if (mode === 'atob') { - originalBuffer = globalThis.Buffer; - // @ts-expect-error Can't assign undefined to BufferConstructor - delete globalThis.Buffer; - } - }); - afterAll(() => { - if (mode === 'atob') { - globalThis.Buffer = originalBuffer; - } - }); - test('toBase64', () => { - const testCases = [ - { - input: 'hello world', - expected: 'aGVsbG8gd29ybGQ=', - }, - { - input: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), - expected: 'aGVsbG8gd29ybGQ=', - }, - { - input: undefined, - expected: '', - }, - { - input: new Uint8Array([ - 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, - 193, 71, - ]), - expected: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', - }, - { - input: '✓', - expected: '4pyT', - }, - { - input: new Uint8Array([226, 156, 147]), - expected: '4pyT', - }, - ]; - - testCases.forEach(({ input, expected }) => { - expect(toBase64(input)).toBe(expected); - }); - }); - - test('fromBase64', () => { - const testCases = [ - { - input: 'aGVsbG8gd29ybGQ=', - expected: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), - }, - { - input: '', - expected: new Uint8Array([]), - }, - { - input: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', - expected: new Uint8Array([ - 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, - 193, 71, - ]), - }, - { - input: '4pyT', - expected: new Uint8Array([226, 156, 147]), - }, - ]; - - testCases.forEach(({ input, expected }) => { - expect(fromBase64(input)).toEqual(expected); - }); - }); -}); diff --git a/tests/buildHeaders.test.ts b/tests/buildHeaders.test.ts deleted file mode 100644 index f9312d6..0000000 --- a/tests/buildHeaders.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { inspect } from 'node:util'; -import { buildHeaders, type HeadersLike, type NullableHeaders } from 'browser-use-sdk/internal/headers'; - -function inspectNullableHeaders(headers: NullableHeaders) { - return `NullableHeaders {${[ - ...[...headers.values.entries()].map(([name, value]) => ` ${inspect(name)}: ${inspect(value)}`), - ...[...headers.nulls].map((name) => ` ${inspect(name)}: null`), - ].join(', ')} }`; -} - -describe('buildHeaders', () => { - const cases: [HeadersLike[], string][] = [ - [[new Headers({ 'content-type': 'text/plain' })], `NullableHeaders { 'content-type': 'text/plain' }`], - [ - [ - { - 'content-type': 'text/plain', - }, - { - 'Content-Type': undefined, - }, - ], - `NullableHeaders { 'content-type': 'text/plain' }`, - ], - [ - [ - { - 'content-type': 'text/plain', - }, - { - 'Content-Type': null, - }, - ], - `NullableHeaders { 'content-type': null }`, - ], - [ - [ - { - cookie: 'name1=value1', - Cookie: 'name2=value2', - }, - ], - `NullableHeaders { 'cookie': 'name2=value2' }`, - ], - [ - [ - { - cookie: 'name1=value1', - Cookie: undefined, - }, - ], - `NullableHeaders { 'cookie': 'name1=value1' }`, - ], - [ - [ - { - cookie: ['name1=value1', 'name2=value2'], - }, - ], - `NullableHeaders { 'cookie': 'name1=value1; name2=value2' }`, - ], - [ - [ - { - 'x-foo': ['name1=value1', 'name2=value2'], - }, - ], - `NullableHeaders { 'x-foo': 'name1=value1, name2=value2' }`, - ], - [ - [ - [ - ['cookie', 'name1=value1'], - ['cookie', 'name2=value2'], - ['Cookie', 'name3=value3'], - ], - ], - `NullableHeaders { 'cookie': 'name1=value1; name2=value2; name3=value3' }`, - ], - [[undefined], `NullableHeaders { }`], - [[null], `NullableHeaders { }`], - ]; - for (const [input, expected] of cases) { - test(expected, () => { - expect(inspectNullableHeaders(buildHeaders(input))).toEqual(expected); - }); - } -}); diff --git a/tests/custom.test.ts b/tests/custom.test.ts new file mode 100644 index 0000000..7f5e031 --- /dev/null +++ b/tests/custom.test.ts @@ -0,0 +1,13 @@ +/** + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ +describe("test", () => { + it("default", () => { + expect(true).toBe(true); + }); +}); diff --git a/tests/form.test.ts b/tests/form.test.ts deleted file mode 100644 index a8b09c9..0000000 --- a/tests/form.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { multipartFormRequestOptions, createForm } from 'browser-use-sdk/internal/uploads'; -import { toFile } from 'browser-use-sdk/core/uploads'; - -describe('form data validation', () => { - test('valid values do not error', async () => { - await multipartFormRequestOptions( - { - body: { - foo: 'foo', - string: 1, - bool: true, - file: await toFile(Buffer.from('some-content')), - blob: new Blob(['Some content'], { type: 'text/plain' }), - }, - }, - fetch, - ); - }); - - test('null', async () => { - await expect(() => - multipartFormRequestOptions( - { - body: { - null: null, - }, - }, - fetch, - ), - ).rejects.toThrow(TypeError); - }); - - test('undefined is stripped', async () => { - const form = await createForm( - { - foo: undefined, - bar: 'baz', - }, - fetch, - ); - expect(form.has('foo')).toBe(false); - expect(form.get('bar')).toBe('baz'); - }); - - test('nested undefined property is stripped', async () => { - const form = await createForm( - { - bar: { - baz: undefined, - }, - }, - fetch, - ); - expect(Array.from(form.entries())).toEqual([]); - - const form2 = await createForm( - { - bar: { - foo: 'string', - baz: undefined, - }, - }, - fetch, - ); - expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); - }); - - test('nested undefined array item is stripped', async () => { - const form = await createForm( - { - bar: [undefined, undefined], - }, - fetch, - ); - expect(Array.from(form.entries())).toEqual([]); - - const form2 = await createForm( - { - bar: [undefined, 'foo'], - }, - fetch, - ); - expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); - }); -}); diff --git a/tests/index.test.ts b/tests/index.test.ts deleted file mode 100644 index 94e7081..0000000 --- a/tests/index.test.ts +++ /dev/null @@ -1,734 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIPromise } from 'browser-use-sdk/core/api-promise'; - -import util from 'node:util'; -import BrowserUse from 'browser-use-sdk'; -import { APIUserAbortError } from 'browser-use-sdk'; -const defaultFetch = fetch; - -describe('instantiate client', () => { - const env = process.env; - - beforeEach(() => { - jest.resetModules(); - process.env = { ...env }; - }); - - afterEach(() => { - process.env = env; - }); - - describe('defaultHeaders', () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - defaultHeaders: { 'X-My-Default-Header': '2' }, - apiKey: 'My API Key', - }); - - test('they are used in the request', async () => { - const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); - expect(req.headers.get('x-my-default-header')).toEqual('2'); - }); - - test('can ignore `undefined` and leave the default', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - headers: { 'X-My-Default-Header': undefined }, - }); - expect(req.headers.get('x-my-default-header')).toEqual('2'); - }); - - test('can be removed with `null`', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - headers: { 'X-My-Default-Header': null }, - }); - expect(req.headers.has('x-my-default-header')).toBe(false); - }); - }); - describe('logging', () => { - const env = process.env; - - beforeEach(() => { - process.env = { ...env }; - process.env['BROWSER_USE_LOG'] = undefined; - }); - - afterEach(() => { - process.env = env; - }); - - const forceAPIResponseForClient = async (client: BrowserUse) => { - await new APIPromise( - client, - Promise.resolve({ - response: new Response(), - controller: new AbortController(), - requestLogID: 'log_000000', - retryOfRequestLogID: undefined, - startTime: Date.now(), - options: { - method: 'get', - path: '/', - }, - }), - ); - }; - - test('debug logs when log level is debug', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - const client = new BrowserUse({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); - - await forceAPIResponseForClient(client); - expect(debugMock).toHaveBeenCalled(); - }); - - test('default logLevel is warn', async () => { - const client = new BrowserUse({ apiKey: 'My API Key' }); - expect(client.logLevel).toBe('warn'); - }); - - test('debug logs are skipped when log level is info', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - const client = new BrowserUse({ logger: logger, logLevel: 'info', apiKey: 'My API Key' }); - - await forceAPIResponseForClient(client); - expect(debugMock).not.toHaveBeenCalled(); - }); - - test('debug logs happen with debug env var', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - process.env['BROWSER_USE_LOG'] = 'debug'; - const client = new BrowserUse({ logger: logger, apiKey: 'My API Key' }); - expect(client.logLevel).toBe('debug'); - - await forceAPIResponseForClient(client); - expect(debugMock).toHaveBeenCalled(); - }); - - test('warn when env var level is invalid', async () => { - const warnMock = jest.fn(); - const logger = { - debug: jest.fn(), - info: jest.fn(), - warn: warnMock, - error: jest.fn(), - }; - - process.env['BROWSER_USE_LOG'] = 'not a log level'; - const client = new BrowserUse({ logger: logger, apiKey: 'My API Key' }); - expect(client.logLevel).toBe('warn'); - expect(warnMock).toHaveBeenCalledWith( - 'process.env[\'BROWSER_USE_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', - ); - }); - - test('client log level overrides env var', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - process.env['BROWSER_USE_LOG'] = 'debug'; - const client = new BrowserUse({ logger: logger, logLevel: 'off', apiKey: 'My API Key' }); - - await forceAPIResponseForClient(client); - expect(debugMock).not.toHaveBeenCalled(); - }); - - test('no warning logged for invalid env var level + valid client level', async () => { - const warnMock = jest.fn(); - const logger = { - debug: jest.fn(), - info: jest.fn(), - warn: warnMock, - error: jest.fn(), - }; - - process.env['BROWSER_USE_LOG'] = 'not a log level'; - const client = new BrowserUse({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); - expect(client.logLevel).toBe('debug'); - expect(warnMock).not.toHaveBeenCalled(); - }); - }); - - describe('defaultQuery', () => { - test('with null query params given', () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - defaultQuery: { apiVersion: 'foo' }, - apiKey: 'My API Key', - }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); - }); - - test('multiple default query params', () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - defaultQuery: { apiVersion: 'foo', hello: 'world' }, - apiKey: 'My API Key', - }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); - }); - - test('overriding with `undefined`', () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - defaultQuery: { hello: 'world' }, - apiKey: 'My API Key', - }); - expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); - }); - }); - - test('custom fetch', async () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', - fetch: (url) => { - return Promise.resolve( - new Response(JSON.stringify({ url, custom: true }), { - headers: { 'Content-Type': 'application/json' }, - }), - ); - }, - }); - - const response = await client.get('/foo'); - expect(response).toEqual({ url: 'http://localhost:5000/foo', custom: true }); - }); - - test('explicit global fetch', async () => { - // make sure the global fetch type is assignable to our Fetch type - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', - fetch: defaultFetch, - }); - }); - - test('custom signal', async () => { - const client = new BrowserUse({ - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', - apiKey: 'My API Key', - fetch: (...args) => { - return new Promise((resolve, reject) => - setTimeout( - () => - defaultFetch(...args) - .then(resolve) - .catch(reject), - 300, - ), - ); - }, - }); - - const controller = new AbortController(); - setTimeout(() => controller.abort(), 200); - - const spy = jest.spyOn(client, 'request'); - - await expect(client.get('/foo', { signal: controller.signal })).rejects.toThrowError(APIUserAbortError); - expect(spy).toHaveBeenCalledTimes(1); - }); - - test('normalized method', async () => { - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - capturedRequest = init; - return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', - fetch: testFetch, - }); - - await client.patch('/foo'); - expect(capturedRequest?.method).toEqual('PATCH'); - }); - - describe('baseUrl', () => { - test('trailing slash', () => { - const client = new BrowserUse({ baseURL: 'http://localhost:5000/custom/path/', apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); - }); - - test('no trailing slash', () => { - const client = new BrowserUse({ baseURL: 'http://localhost:5000/custom/path', apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); - }); - - afterEach(() => { - process.env['BROWSER_USE_BASE_URL'] = undefined; - }); - - test('explicit option', () => { - const client = new BrowserUse({ baseURL: 'https://example.com', apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://example.com'); - }); - - test('env variable', () => { - process.env['BROWSER_USE_BASE_URL'] = 'https://example.com/from_env'; - const client = new BrowserUse({ apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://example.com/from_env'); - }); - - test('empty env variable', () => { - process.env['BROWSER_USE_BASE_URL'] = ''; // empty - const client = new BrowserUse({ apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://api.browser-use.com/api/v2'); - }); - - test('blank env variable', () => { - process.env['BROWSER_USE_BASE_URL'] = ' '; // blank - const client = new BrowserUse({ apiKey: 'My API Key' }); - expect(client.baseURL).toEqual('https://api.browser-use.com/api/v2'); - }); - - test('in request options', () => { - const client = new BrowserUse({ apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/option/foo', - ); - }); - - test('in request options overridden by client options', () => { - const client = new BrowserUse({ apiKey: 'My API Key', baseURL: 'http://localhost:5000/client' }); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/client/foo', - ); - }); - - test('in request options overridden by env variable', () => { - process.env['BROWSER_USE_BASE_URL'] = 'http://localhost:5000/env'; - const client = new BrowserUse({ apiKey: 'My API Key' }); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/env/foo', - ); - }); - }); - - test('maxRetries option is correctly set', () => { - const client = new BrowserUse({ maxRetries: 4, apiKey: 'My API Key' }); - expect(client.maxRetries).toEqual(4); - - // default - const client2 = new BrowserUse({ apiKey: 'My API Key' }); - expect(client2.maxRetries).toEqual(2); - }); - - describe('withOptions', () => { - test('creates a new client with overridden options', async () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - maxRetries: 3, - apiKey: 'My API Key', - }); - - const newClient = client.withOptions({ - maxRetries: 5, - baseURL: 'http://localhost:5001/', - }); - - // Verify the new client has updated options - expect(newClient.maxRetries).toEqual(5); - expect(newClient.baseURL).toEqual('http://localhost:5001/'); - - // Verify the original client is unchanged - expect(client.maxRetries).toEqual(3); - expect(client.baseURL).toEqual('http://localhost:5000/'); - - // Verify it's a different instance - expect(newClient).not.toBe(client); - expect(newClient.constructor).toBe(client.constructor); - }); - - test('inherits options from the parent client', async () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - defaultHeaders: { 'X-Test-Header': 'test-value' }, - defaultQuery: { 'test-param': 'test-value' }, - apiKey: 'My API Key', - }); - - const newClient = client.withOptions({ - baseURL: 'http://localhost:5001/', - }); - - // Test inherited options remain the same - expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value'); - - const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' }); - expect(req.headers.get('x-test-header')).toEqual('test-value'); - }); - - test('respects runtime property changes when creating new client', () => { - const client = new BrowserUse({ - baseURL: 'http://localhost:5000/', - timeout: 1000, - apiKey: 'My API Key', - }); - - // Modify the client properties directly after creation - client.baseURL = 'http://localhost:6000/'; - client.timeout = 2000; - - // Create a new client with withOptions - const newClient = client.withOptions({ - maxRetries: 10, - }); - - // Verify the new client uses the updated properties, not the original ones - expect(newClient.baseURL).toEqual('http://localhost:6000/'); - expect(newClient.timeout).toEqual(2000); - expect(newClient.maxRetries).toEqual(10); - - // Original client should still have its modified properties - expect(client.baseURL).toEqual('http://localhost:6000/'); - expect(client.timeout).toEqual(2000); - expect(client.maxRetries).not.toEqual(10); - - // Verify URL building uses the updated baseURL - expect(newClient.buildURL('/bar', null)).toEqual('http://localhost:6000/bar'); - }); - }); - - test('with environment variable arguments', () => { - // set options via env var - process.env['BROWSER_USE_API_KEY'] = 'My API Key'; - const client = new BrowserUse(); - expect(client.apiKey).toBe('My API Key'); - }); - - test('with overridden environment variable arguments', () => { - // set options via env var - process.env['BROWSER_USE_API_KEY'] = 'another My API Key'; - const client = new BrowserUse({ apiKey: 'My API Key' }); - expect(client.apiKey).toBe('My API Key'); - }); -}); - -describe('request building', () => { - const client = new BrowserUse({ apiKey: 'My API Key' }); - - describe('custom headers', () => { - test('handles undefined', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: { value: 'hello' }, - headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, - }); - expect(req.headers.get('x-foo')).toEqual('bar'); - expect(req.headers.get('x-Foo')).toEqual('bar'); - expect(req.headers.get('X-Foo')).toEqual('bar'); - expect(req.headers.get('x-baz')).toEqual(null); - }); - }); -}); - -describe('default encoder', () => { - const client = new BrowserUse({ apiKey: 'My API Key' }); - - class Serializable { - toJSON() { - return { $type: 'Serializable' }; - } - } - class Collection { - #things: T[]; - constructor(things: T[]) { - this.#things = Array.from(things); - } - toJSON() { - return Array.from(this.#things); - } - [Symbol.iterator]() { - return this.#things[Symbol.iterator]; - } - } - for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { - test(`serializes ${util.inspect(jsonValue)} as json`, async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: jsonValue, - }); - expect(req.headers).toBeInstanceOf(Headers); - expect(req.headers.get('content-type')).toEqual('application/json'); - expect(req.body).toBe(JSON.stringify(jsonValue)); - }); - } - - const encoder = new TextEncoder(); - const asyncIterable = (async function* () { - yield encoder.encode('a\n'); - yield encoder.encode('b\n'); - yield encoder.encode('c\n'); - })(); - for (const streamValue of [ - [encoder.encode('a\nb\nc\n')][Symbol.iterator](), - new Response('a\nb\nc\n').body, - asyncIterable, - ]) { - test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: streamValue, - }); - expect(req.headers).toBeInstanceOf(Headers); - expect(req.headers.get('content-type')).toEqual(null); - expect(req.body).toBeInstanceOf(ReadableStream); - expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); - }); - } - - test(`can set content-type for ReadableStream`, async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: new Response('a\nb\nc\n').body, - headers: { 'Content-Type': 'text/plain' }, - }); - expect(req.headers).toBeInstanceOf(Headers); - expect(req.headers.get('content-type')).toEqual('text/plain'); - expect(req.body).toBeInstanceOf(ReadableStream); - expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); - }); -}); - -describe('retries', () => { - test('retry on timeout', async () => { - let count = 0; - const testFetch = async ( - url: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Promise((resolve, reject) => - signal?.addEventListener('abort', () => reject(new Error('timed out'))), - ); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new BrowserUse({ apiKey: 'My API Key', timeout: 10, fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); - - test('retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new BrowserUse({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - - expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('2'); - expect(count).toEqual(3); - }); - - test('omit retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new BrowserUse({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - headers: { 'X-Stainless-Retry-Count': null }, - }), - ).toEqual({ a: 1 }); - - expect((capturedRequest!.headers as Headers).has('x-stainless-retry-count')).toBe(false); - }); - - test('omit retry count header by default', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new BrowserUse({ - apiKey: 'My API Key', - fetch: testFetch, - maxRetries: 4, - defaultHeaders: { 'X-Stainless-Retry-Count': null }, - }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - }), - ).toEqual({ a: 1 }); - - expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); - }); - - test('overwrite retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new BrowserUse({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - headers: { 'X-Stainless-Retry-Count': '42' }, - }), - ).toEqual({ a: 1 }); - - expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('42'); - }); - - test('retry on 429 with retry-after', async () => { - let count = 0; - const testFetch = async ( - url: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new BrowserUse({ apiKey: 'My API Key', fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); - - test('retry on 429 with retry-after-ms', async () => { - let count = 0; - const testFetch = async ( - url: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After-Ms': '10', - }, - }); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new BrowserUse({ apiKey: 'My API Key', fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); -}); diff --git a/tests/lib/webhooks.test.ts b/tests/lib/webhooks.test.ts deleted file mode 100644 index a618775..0000000 --- a/tests/lib/webhooks.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - createWebhookSignature, - verifyWebhookEventSignature, - zWebhookSchema, - zWebhookTimestamp, -} from '../../src/lib/webhooks'; - -describe('webhooks', () => { - describe('parse', () => { - test('timestamp', () => { - expect(zWebhookTimestamp.parse('2025-05-25T09:22:22.269116+00:00')).toBeDefined(); - expect(zWebhookTimestamp.parse('2025-08-15T18:09:11.881540')).toBeDefined(); - }); - - test('agent.task.status_update', () => { - const MOCK: unknown = { - type: 'agent.task.status_update', - timestamp: '2025-05-25T09:22:22.269116+00:00', - payload: { - session_id: 'cd9cc7bf-e3af-4181-80a2-73f083bc94b4', - task_id: '5b73fb3f-a3cb-4912-be40-17ce9e9e1a45', - status: 'finished', - metadata: { - campaign: 'q4-automation', - team: 'marketing', - }, - }, - }; - - const response = zWebhookSchema.parse(MOCK); - - expect(response).toBeDefined(); - }); - - test('test', () => { - const MOCK: unknown = { - type: 'test', - timestamp: '2025-05-25T09:22:22.269116+00:00', - payload: { test: 'ok' }, - }; - - const response = zWebhookSchema.parse(MOCK); - - expect(response).toBeDefined(); - }); - - test('invalid', () => { - const MOCK: unknown = { - type: 'invalid', - timestamp: '2025-05-25T09:22:22.269116+00:00', - payload: { test: 'ok' }, - }; - - expect(() => zWebhookSchema.parse(MOCK)).toThrow(); - }); - }); - - describe('verify', () => { - test('correctly calculates signature', async () => { - const timestamp = '2025-05-26:22:22.269116+00:00'; - - const MOCK = { - type: 'agent.task.status_update', - timestamp: '2025-05-25T09:22:22.269116+00:00', - payload: { - session_id: 'cd9cc7bf-e3af-4181-80a2-73f083bc94b4', - task_id: '5b73fb3f-a3cb-4912-be40-17ce9e9e1a45', - status: 'finished', - metadata: { - campaign: 'q4-automation', - team: 'marketing', - }, - }, - }; - - const signature = createWebhookSignature({ - payload: MOCK.payload, - secret: 'secret', - timestamp, - }); - - const validJSON = await verifyWebhookEventSignature( - { - body: MOCK, - signature: signature, - timestamp, - }, - { secret: 'secret' }, - ); - - const validString = await verifyWebhookEventSignature( - { - body: JSON.stringify(MOCK), - signature: signature, - timestamp, - }, - { secret: 'secret' }, - ); - - const invalid = await verifyWebhookEventSignature( - { - body: JSON.stringify(MOCK), - signature: 'invalid', - timestamp, - }, - { secret: 'secret' }, - ); - - expect(validJSON.ok).toBe(true); - expect(validString.ok).toBe(true); - expect(invalid.ok).toBe(false); - }); - }); -}); diff --git a/tests/mock-server/MockServer.ts b/tests/mock-server/MockServer.ts new file mode 100644 index 0000000..6e258f1 --- /dev/null +++ b/tests/mock-server/MockServer.ts @@ -0,0 +1,29 @@ +import { RequestHandlerOptions } from "msw"; +import type { SetupServer } from "msw/node"; + +import { mockEndpointBuilder } from "./mockEndpointBuilder"; + +export interface MockServerOptions { + baseUrl: string; + server: SetupServer; +} + +export class MockServer { + private readonly server: SetupServer; + public readonly baseUrl: string; + + constructor({ baseUrl, server }: MockServerOptions) { + this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl; + this.server = server; + } + + public mockEndpoint(options?: RequestHandlerOptions): ReturnType { + const builder = mockEndpointBuilder({ + once: options?.once, + onBuild: (handler) => { + this.server.use(handler); + }, + }).baseUrl(this.baseUrl); + return builder; + } +} diff --git a/tests/mock-server/MockServerPool.ts b/tests/mock-server/MockServerPool.ts new file mode 100644 index 0000000..8160806 --- /dev/null +++ b/tests/mock-server/MockServerPool.ts @@ -0,0 +1,106 @@ +import { setupServer } from "msw/node"; + +import { fromJson, toJson } from "../../src/core/json"; +import { MockServer } from "./MockServer"; +import { randomBaseUrl } from "./randomBaseUrl"; + +const mswServer = setupServer(); +interface MockServerOptions { + baseUrl?: string; +} + +async function formatHttpRequest(request: Request, id?: string): Promise { + try { + const clone = request.clone(); + const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); + + let body = ""; + try { + const contentType = clone.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = toJson(fromJson(await clone.text()), undefined, 2); + } else if (clone.body) { + body = await clone.text(); + } + } catch (e) { + body = "(unable to parse body)"; + } + + const title = id ? `### Request ${id} ###\n` : ""; + const firstLine = `${title}${request.method} ${request.url.toString()} HTTP/1.1`; + + return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; + } catch (e) { + return `Error formatting request: ${e}`; + } +} + +async function formatHttpResponse(response: Response, id?: string): Promise { + try { + const clone = response.clone(); + const headers = [...clone.headers.entries()].map(([k, v]) => `${k}: ${v}`).join("\n"); + + let body = ""; + try { + const contentType = clone.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = toJson(fromJson(await clone.text()), undefined, 2); + } else if (clone.body) { + body = await clone.text(); + } + } catch (e) { + body = "(unable to parse body)"; + } + + const title = id ? `### Response for ${id} ###\n` : ""; + const firstLine = `${title}HTTP/1.1 ${response.status} ${response.statusText}`; + + return `\n${firstLine}\n${headers}\n\n${body || "(no body)"}\n`; + } catch (e) { + return `Error formatting response: ${e}`; + } +} + +class MockServerPool { + private servers: MockServer[] = []; + + public createServer(options?: Partial): MockServer { + const baseUrl = options?.baseUrl || randomBaseUrl(); + const server = new MockServer({ baseUrl, server: mswServer }); + this.servers.push(server); + return server; + } + + public getServers(): MockServer[] { + return [...this.servers]; + } + + public listen(): void { + const onUnhandledRequest = process.env.LOG_LEVEL === "debug" ? "warn" : "bypass"; + mswServer.listen({ onUnhandledRequest }); + + if (process.env.LOG_LEVEL === "debug") { + mswServer.events.on("request:start", async ({ request, requestId }) => { + const formattedRequest = await formatHttpRequest(request, requestId); + console.debug("request:start\n" + formattedRequest); + }); + + mswServer.events.on("request:unhandled", async ({ request, requestId }) => { + const formattedRequest = await formatHttpRequest(request, requestId); + console.debug("request:unhandled\n" + formattedRequest); + }); + + mswServer.events.on("response:mocked", async ({ request, response, requestId }) => { + const formattedResponse = await formatHttpResponse(response, requestId); + console.debug("response:mocked\n" + formattedResponse); + }); + } + } + + public close(): void { + this.servers = []; + mswServer.close(); + } +} + +export const mockServerPool = new MockServerPool(); diff --git a/tests/mock-server/mockEndpointBuilder.ts b/tests/mock-server/mockEndpointBuilder.ts new file mode 100644 index 0000000..0b069b2 --- /dev/null +++ b/tests/mock-server/mockEndpointBuilder.ts @@ -0,0 +1,210 @@ +import { DefaultBodyType, HttpHandler, HttpResponse, HttpResponseResolver, http } from "msw"; + +import { url } from "../../src/core"; +import { toJson } from "../../src/core/json"; +import { withHeaders } from "./withHeaders"; +import { withJson } from "./withJson"; + +type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; + +interface MethodStage { + baseUrl(baseUrl: string): MethodStage; + all(path: string): RequestHeadersStage; + get(path: string): RequestHeadersStage; + post(path: string): RequestHeadersStage; + put(path: string): RequestHeadersStage; + delete(path: string): RequestHeadersStage; + patch(path: string): RequestHeadersStage; + options(path: string): RequestHeadersStage; + head(path: string): RequestHeadersStage; +} + +interface RequestHeadersStage extends RequestBodyStage, ResponseStage { + header(name: string, value: string): RequestHeadersStage; + headers(headers: Record): RequestBodyStage; +} + +interface RequestBodyStage extends ResponseStage { + jsonBody(body: unknown): ResponseStage; +} + +interface ResponseStage { + respondWith(): ResponseStatusStage; +} +interface ResponseStatusStage { + statusCode(statusCode: number): ResponseHeaderStage; +} + +interface ResponseHeaderStage extends ResponseBodyStage, BuildStage { + header(name: string, value: string): ResponseHeaderStage; + headers(headers: Record): ResponseHeaderStage; +} + +interface ResponseBodyStage { + jsonBody(body: unknown): BuildStage; +} + +interface BuildStage { + build(): HttpHandler; +} + +export interface HttpHandlerBuilderOptions { + onBuild?: (handler: HttpHandler) => void; + once?: boolean; +} + +class RequestBuilder implements MethodStage, RequestHeadersStage, RequestBodyStage, ResponseStage { + private method: HttpMethod = "get"; + private _baseUrl: string = ""; + private path: string = "/"; + private readonly predicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[] = []; + private readonly handlerOptions?: HttpHandlerBuilderOptions; + + constructor(options?: HttpHandlerBuilderOptions) { + this.handlerOptions = options; + } + + baseUrl(baseUrl: string): MethodStage { + this._baseUrl = baseUrl; + return this; + } + + all(path: string): RequestHeadersStage { + this.method = "all"; + this.path = path; + return this; + } + + get(path: string): RequestHeadersStage { + this.method = "get"; + this.path = path; + return this; + } + + post(path: string): RequestHeadersStage { + this.method = "post"; + this.path = path; + return this; + } + + put(path: string): RequestHeadersStage { + this.method = "put"; + this.path = path; + return this; + } + + delete(path: string): RequestHeadersStage { + this.method = "delete"; + this.path = path; + return this; + } + + patch(path: string): RequestHeadersStage { + this.method = "patch"; + this.path = path; + return this; + } + + options(path: string): RequestHeadersStage { + this.method = "options"; + this.path = path; + return this; + } + + head(path: string): RequestHeadersStage { + this.method = "head"; + this.path = path; + return this; + } + + header(name: string, value: string): RequestHeadersStage { + this.predicates.push((resolver) => withHeaders({ [name]: value }, resolver)); + return this; + } + + headers(headers: Record): RequestBodyStage { + this.predicates.push((resolver) => withHeaders(headers, resolver)); + return this; + } + + jsonBody(body: unknown): ResponseStage { + if (body === undefined) { + throw new Error("Undefined is not valid JSON. Do not call jsonBody if you want an empty body."); + } + this.predicates.push((resolver) => withJson(body, resolver)); + return this; + } + + respondWith(): ResponseStatusStage { + return new ResponseBuilder(this.method, this.buildUrl(), this.predicates, this.handlerOptions); + } + + private buildUrl(): string { + return url.join(this._baseUrl, this.path); + } +} + +class ResponseBuilder implements ResponseStatusStage, ResponseHeaderStage, ResponseBodyStage, BuildStage { + private readonly method: HttpMethod; + private readonly url: string; + private readonly requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[]; + private readonly handlerOptions?: HttpHandlerBuilderOptions; + + private responseStatusCode: number = 200; + private responseHeaders: Record = {}; + private responseBody: DefaultBodyType = undefined; + + constructor( + method: HttpMethod, + url: string, + requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[], + options?: HttpHandlerBuilderOptions, + ) { + this.method = method; + this.url = url; + this.requestPredicates = requestPredicates; + this.handlerOptions = options; + } + + public statusCode(code: number): ResponseHeaderStage { + this.responseStatusCode = code; + return this; + } + + public header(name: string, value: string): ResponseHeaderStage { + this.responseHeaders[name] = value; + return this; + } + + public headers(headers: Record): ResponseHeaderStage { + this.responseHeaders = { ...this.responseHeaders, ...headers }; + return this; + } + + public jsonBody(body: unknown): BuildStage { + if (body === undefined) { + throw new Error("Undefined is not valid JSON. Do not call jsonBody if you expect an empty body."); + } + this.responseBody = toJson(body); + return this; + } + + public build(): HttpHandler { + const responseResolver: HttpResponseResolver = () => { + return new HttpResponse(this.responseBody, { + status: this.responseStatusCode, + headers: this.responseHeaders, + }); + }; + + const finalResolver = this.requestPredicates.reduceRight((acc, predicate) => predicate(acc), responseResolver); + + const handler = http[this.method](this.url, finalResolver, this.handlerOptions); + this.handlerOptions?.onBuild?.(handler); + return handler; + } +} + +export function mockEndpointBuilder(options?: HttpHandlerBuilderOptions): MethodStage { + return new RequestBuilder(options); +} diff --git a/tests/mock-server/randomBaseUrl.ts b/tests/mock-server/randomBaseUrl.ts new file mode 100644 index 0000000..031aa64 --- /dev/null +++ b/tests/mock-server/randomBaseUrl.ts @@ -0,0 +1,4 @@ +export function randomBaseUrl(): string { + const randomString = Math.random().toString(36).substring(2, 15); + return `http://${randomString}.localhost`; +} diff --git a/tests/mock-server/setup.ts b/tests/mock-server/setup.ts new file mode 100644 index 0000000..c216d60 --- /dev/null +++ b/tests/mock-server/setup.ts @@ -0,0 +1,10 @@ +import { afterAll, beforeAll } from "@jest/globals"; + +import { mockServerPool } from "./MockServerPool"; + +beforeAll(() => { + mockServerPool.listen(); +}); +afterAll(() => { + mockServerPool.close(); +}); diff --git a/tests/mock-server/withHeaders.ts b/tests/mock-server/withHeaders.ts new file mode 100644 index 0000000..e77c837 --- /dev/null +++ b/tests/mock-server/withHeaders.ts @@ -0,0 +1,70 @@ +import { HttpResponseResolver, passthrough } from "msw"; + +/** + * Creates a request matcher that validates if request headers match specified criteria + * @param expectedHeaders - Headers to match against + * @param resolver - Response resolver to execute if headers match + */ +export function withHeaders( + expectedHeaders: Record boolean)>, + resolver: HttpResponseResolver, +): HttpResponseResolver { + return (args) => { + const { request } = args; + const { headers } = request; + + const mismatches: Record< + string, + { actual: string | null; expected: string | RegExp | ((value: string) => boolean) } + > = {}; + + for (const [key, expectedValue] of Object.entries(expectedHeaders)) { + const actualValue = headers.get(key); + + if (actualValue === null) { + mismatches[key] = { actual: null, expected: expectedValue }; + continue; + } + + if (typeof expectedValue === "function") { + if (!expectedValue(actualValue)) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } else if (expectedValue instanceof RegExp) { + if (!expectedValue.test(actualValue)) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } else if (expectedValue !== actualValue) { + mismatches[key] = { actual: actualValue, expected: expectedValue }; + } + } + + if (Object.keys(mismatches).length > 0) { + const formattedMismatches = formatHeaderMismatches(mismatches); + console.error("Header mismatch:", formattedMismatches); + return passthrough(); + } + + return resolver(args); + }; +} + +function formatHeaderMismatches( + mismatches: Record boolean) }>, +): Record { + const formatted: Record = {}; + + for (const [key, { actual, expected }] of Object.entries(mismatches)) { + formatted[key] = { + actual, + expected: + expected instanceof RegExp + ? expected.toString() + : typeof expected === "function" + ? "[Function]" + : expected, + }; + } + + return formatted; +} diff --git a/tests/mock-server/withJson.ts b/tests/mock-server/withJson.ts new file mode 100644 index 0000000..bfcd9a6 --- /dev/null +++ b/tests/mock-server/withJson.ts @@ -0,0 +1,158 @@ +import { HttpResponseResolver, passthrough } from "msw"; + +import { fromJson, toJson } from "../../src/core/json"; + +/** + * Creates a request matcher that validates if the request JSON body exactly matches the expected object + * @param expectedBody - The exact body object to match against + * @param resolver - Response resolver to execute if body matches + */ +export function withJson(expectedBody: unknown, resolver: HttpResponseResolver): HttpResponseResolver { + return async (args) => { + const { request } = args; + + let clonedRequest: Request; + let bodyText: string | undefined; + let actualBody: unknown; + try { + clonedRequest = request.clone(); + bodyText = await clonedRequest.text(); + if (bodyText === "") { + console.error("Request body is empty, expected a JSON object."); + return passthrough(); + } + actualBody = fromJson(bodyText); + } catch (error) { + console.error(`Error processing request body:\n\tError: ${error}\n\tBody: ${bodyText}`); + return passthrough(); + } + + const mismatches = findMismatches(actualBody, expectedBody); + if (Object.keys(mismatches).length > 0) { + console.error("JSON body mismatch:", toJson(mismatches, undefined, 2)); + return passthrough(); + } + + return resolver(args); + }; +} + +function findMismatches(actual: any, expected: any): Record { + const mismatches: Record = {}; + + if (typeof actual !== typeof expected) { + if (areEquivalent(actual, expected)) { + return {}; + } + return { value: { actual, expected } }; + } + + if (typeof actual !== "object" || actual === null || expected === null) { + if (actual !== expected) { + if (areEquivalent(actual, expected)) { + return {}; + } + return { value: { actual, expected } }; + } + return {}; + } + + if (Array.isArray(actual) && Array.isArray(expected)) { + if (actual.length !== expected.length) { + return { length: { actual: actual.length, expected: expected.length } }; + } + + const arrayMismatches: Record = {}; + for (let i = 0; i < actual.length; i++) { + const itemMismatches = findMismatches(actual[i], expected[i]); + if (Object.keys(itemMismatches).length > 0) { + for (const [mismatchKey, mismatchValue] of Object.entries(itemMismatches)) { + arrayMismatches[`[${i}]${mismatchKey === "value" ? "" : "." + mismatchKey}`] = mismatchValue; + } + } + } + return arrayMismatches; + } + + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + + const allKeys = new Set([...actualKeys, ...expectedKeys]); + + for (const key of allKeys) { + if (!expectedKeys.includes(key)) { + if (actual[key] === undefined) { + continue; // Skip undefined values in actual + } + mismatches[key] = { actual: actual[key], expected: undefined }; + } else if (!actualKeys.includes(key)) { + if (expected[key] === undefined) { + continue; // Skip undefined values in expected + } + mismatches[key] = { actual: undefined, expected: expected[key] }; + } else if ( + typeof actual[key] === "object" && + actual[key] !== null && + typeof expected[key] === "object" && + expected[key] !== null + ) { + const nestedMismatches = findMismatches(actual[key], expected[key]); + if (Object.keys(nestedMismatches).length > 0) { + for (const [nestedKey, nestedValue] of Object.entries(nestedMismatches)) { + mismatches[`${key}${nestedKey === "value" ? "" : "." + nestedKey}`] = nestedValue; + } + } + } else if (actual[key] !== expected[key]) { + if (areEquivalent(actual[key], expected[key])) { + continue; + } + mismatches[key] = { actual: actual[key], expected: expected[key] }; + } + } + + return mismatches; +} + +function areEquivalent(actual: unknown, expected: unknown): boolean { + if (actual === expected) { + return true; + } + if (isEquivalentBigInt(actual, expected)) { + return true; + } + if (isEquivalentDatetime(actual, expected)) { + return true; + } + return false; +} + +function isEquivalentBigInt(actual: unknown, expected: unknown) { + if (typeof actual === "number") { + actual = BigInt(actual); + } + if (typeof expected === "number") { + expected = BigInt(expected); + } + if (typeof actual === "bigint" && typeof expected === "bigint") { + return actual === expected; + } + return false; +} + +function isEquivalentDatetime(str1: unknown, str2: unknown): boolean { + if (typeof str1 !== "string" || typeof str2 !== "string") { + return false; + } + const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/; + if (!isoDatePattern.test(str1) || !isoDatePattern.test(str2)) { + return false; + } + + try { + const date1 = new Date(str1).getTime(); + const date2 = new Date(str2).getTime(); + return date1 === date2; + } catch { + return false; + } +} diff --git a/tests/path.test.ts b/tests/path.test.ts deleted file mode 100644 index 2adc998..0000000 --- a/tests/path.test.ts +++ /dev/null @@ -1,462 +0,0 @@ -import { createPathTagFunction, encodeURIPath } from 'browser-use-sdk/internal/utils/path'; -import { inspect } from 'node:util'; -import { runInNewContext } from 'node:vm'; - -describe('path template tag function', () => { - test('validates input', () => { - const testParams = ['', '.', '..', 'x', '%2e', '%2E', '%2e%2e', '%2E%2e', '%2e%2E', '%2E%2E']; - const testCases = [ - ['/path_params/', '/a'], - ['/path_params/', '/'], - ['/path_params/', ''], - ['', '/a'], - ['', '/'], - ['', ''], - ['a'], - [''], - ['/path_params/', ':initiate'], - ['/path_params/', '.json'], - ['/path_params/', '?beta=true'], - ['/path_params/', '.?beta=true'], - ['/path_params/', '/', '/download'], - ['/path_params/', '-', '/download'], - ['/path_params/', '', '/download'], - ['/path_params/', '.', '/download'], - ['/path_params/', '..', '/download'], - ['/plain/path'], - ]; - - function paramPermutations(len: number): string[][] { - if (len === 0) return []; - if (len === 1) return testParams.map((e) => [e]); - const rest = paramPermutations(len - 1); - return testParams.flatMap((e) => rest.map((r) => [e, ...r])); - } - - // We need to test how %2E is handled, so we use a custom encoder that does no escaping. - const rawPath = createPathTagFunction((s) => s); - - const emptyObject = {}; - const mathObject = Math; - const numberObject = new Number(); - const stringObject = new String(); - const basicClass = new (class {})(); - const classWithToString = new (class { - toString() { - return 'ok'; - } - })(); - - // Invalid values - expect(() => rawPath`/a/${null}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Null is not a valid path parameter\n' + - '/a/null/b\n' + - ' ^^^^', - ); - expect(() => rawPath`/a/${undefined}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Undefined is not a valid path parameter\n' + - '/a/undefined/b\n' + - ' ^^^^^^^^^', - ); - expect(() => rawPath`/a/${emptyObject}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/a/[object Object]/b\n' + - ' ^^^^^^^^^^^^^^^', - ); - expect(() => rawPath`?${mathObject}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Math is not a valid path parameter\n' + - '?[object Math]\n' + - ' ^^^^^^^^^^^^^', - ); - expect(() => rawPath`/${basicClass}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/[object Object]\n' + - ' ^^^^^^^^^^^^^^', - ); - expect(() => rawPath`/../${''}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '/../\n' + - ' ^^', - ); - expect(() => rawPath`/../${{}}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - 'Value of type Object is not a valid path parameter\n' + - '/../[object Object]\n' + - ' ^^ ^^^^^^^^^^^^^^', - ); - - // Valid values - expect(rawPath`/${0}`).toBe('/0'); - expect(rawPath`/${''}`).toBe('/'); - expect(rawPath`/${numberObject}`).toBe('/0'); - expect(rawPath`${stringObject}/`).toBe('/'); - expect(rawPath`/${classWithToString}`).toBe('/ok'); - - // We need to check what happens with cross-realm values, which we might get from - // Jest or other frames in a browser. - - const newRealm = runInNewContext('globalThis'); - expect(newRealm.Object).not.toBe(Object); - - const crossRealmObject = newRealm.Object(); - const crossRealmMathObject = newRealm.Math; - const crossRealmNumber = new newRealm.Number(); - const crossRealmString = new newRealm.String(); - const crossRealmClass = new (class extends newRealm.Object {})(); - const crossRealmClassWithToString = new (class extends newRealm.Object { - toString() { - return 'ok'; - } - })(); - - // Invalid cross-realm values - expect(() => rawPath`/a/${crossRealmObject}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/a/[object Object]/b\n' + - ' ^^^^^^^^^^^^^^^', - ); - expect(() => rawPath`?${crossRealmMathObject}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Math is not a valid path parameter\n' + - '?[object Math]\n' + - ' ^^^^^^^^^^^^^', - ); - expect(() => rawPath`/${crossRealmClass}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/[object Object]\n' + - ' ^^^^^^^^^^^^^^^', - ); - - // Valid cross-realm values - expect(rawPath`/${crossRealmNumber}`).toBe('/0'); - expect(rawPath`${crossRealmString}/`).toBe('/'); - expect(rawPath`/${crossRealmClassWithToString}`).toBe('/ok'); - - const results: { - [pathParts: string]: { - [params: string]: { valid: boolean; result?: string; error?: string }; - }; - } = {}; - - for (const pathParts of testCases) { - const pathResults: Record = {}; - results[JSON.stringify(pathParts)] = pathResults; - for (const params of paramPermutations(pathParts.length - 1)) { - const stringRaw = String.raw({ raw: pathParts }, ...params); - const plainString = String.raw( - { raw: pathParts.map((e) => e.replace(/\./g, 'x')) }, - ...params.map((e) => 'X'.repeat(e.length)), - ); - const normalizedStringRaw = new URL(stringRaw, 'https://example.com').href; - const normalizedPlainString = new URL(plainString, 'https://example.com').href; - const pathResultsKey = JSON.stringify(params); - try { - const result = rawPath(pathParts, ...params); - expect(result).toBe(stringRaw); - // there are no special segments, so the length of the normalized path is - // equal to the length of the normalized plain path. - expect(normalizedStringRaw.length).toBe(normalizedPlainString.length); - pathResults[pathResultsKey] = { - valid: true, - result, - }; - } catch (e) { - const error = String(e); - expect(error).toMatch(/Path parameters result in path with invalid segment/); - // there are special segments, so the length of the normalized path is - // different than the length of the normalized plain path. - expect(normalizedStringRaw.length).not.toBe(normalizedPlainString.length); - pathResults[pathResultsKey] = { - valid: false, - error, - }; - } - } - } - - expect(results).toMatchObject({ - '["/path_params/","/a"]': { - '["x"]': { valid: true, result: '/path_params/x/a' }, - '[""]': { valid: true, result: '/path_params//a' }, - '["%2E%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2e/a\n' + - ' ^^^^^^', - }, - '["%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E/a\n' + - ' ^^^', - }, - }, - '["/path_params/","/"]': { - '["x"]': { valid: true, result: '/path_params/x/' }, - '[""]': { valid: true, result: '/path_params//' }, - '["%2e%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2e%2E/\n' + - ' ^^^^^^', - }, - '["%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2e/\n' + - ' ^^^', - }, - }, - '["/path_params/",""]': { - '[""]': { valid: true, result: '/path_params/' }, - '["x"]': { valid: true, result: '/path_params/x' }, - '["%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E\n' + - ' ^^^', - }, - '["%2E%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2e\n' + - ' ^^^^^^', - }, - }, - '["","/a"]': { - '[""]': { valid: true, result: '/a' }, - '["x"]': { valid: true, result: 'x/a' }, - '["%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n%2E/a\n^^^', - }, - '["%2e%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + - '%2e%2E/a\n' + - '^^^^^^', - }, - }, - '["","/"]': { - '["x"]': { valid: true, result: 'x/' }, - '[""]': { valid: true, result: '/' }, - '["%2E%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - '%2E%2e/\n' + - '^^^^^^', - }, - '["."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - './\n^', - }, - }, - '["",""]': { - '[""]': { valid: true, result: '' }, - '["x"]': { valid: true, result: 'x' }, - '[".."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '..\n^^', - }, - '["."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - '.\n^', - }, - }, - '["a"]': {}, - '[""]': {}, - '["/path_params/",":initiate"]': { - '[""]': { valid: true, result: '/path_params/:initiate' }, - '["."]': { valid: true, result: '/path_params/.:initiate' }, - }, - '["/path_params/",".json"]': { - '["x"]': { valid: true, result: '/path_params/x.json' }, - '["."]': { valid: true, result: '/path_params/..json' }, - }, - '["/path_params/","?beta=true"]': { - '["x"]': { valid: true, result: '/path_params/x?beta=true' }, - '[""]': { valid: true, result: '/path_params/?beta=true' }, - '["%2E%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2E?beta=true\n' + - ' ^^^^^^', - }, - '["%2e%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2e%2E?beta=true\n' + - ' ^^^^^^', - }, - }, - '["/path_params/",".?beta=true"]': { - '[".."]': { valid: true, result: '/path_params/...?beta=true' }, - '["x"]': { valid: true, result: '/path_params/x.?beta=true' }, - '[""]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - '/path_params/.?beta=true\n' + - ' ^', - }, - '["%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e." can\'t be safely passed as a path parameter\n' + - '/path_params/%2e.?beta=true\n' + - ' ^^^^', - }, - }, - '["/path_params/","/","/download"]': { - '["",""]': { valid: true, result: '/path_params///download' }, - '["","x"]': { valid: true, result: '/path_params//x/download' }, - '[".","%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - 'Value "%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/./%2e/download\n' + - ' ^ ^^^', - }, - '["%2E%2e","%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - 'Value "%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2e/%2e/download\n' + - ' ^^^^^^ ^^^', - }, - }, - '["/path_params/","-","/download"]': { - '["","%2e"]': { valid: true, result: '/path_params/-%2e/download' }, - '["%2E",".."]': { valid: true, result: '/path_params/%2E-../download' }, - }, - '["/path_params/","","/download"]': { - '["%2E%2e","%2e%2E"]': { valid: true, result: '/path_params/%2E%2e%2e%2E/download' }, - '["%2E",".."]': { valid: true, result: '/path_params/%2E../download' }, - '["","%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E/download\n' + - ' ^^^', - }, - '["%2E","."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E." can\'t be safely passed as a path parameter\n' + - '/path_params/%2E./download\n' + - ' ^^^^', - }, - }, - '["/path_params/",".","/download"]': { - '["%2e%2e",""]': { valid: true, result: '/path_params/%2e%2e./download' }, - '["","%2e%2e"]': { valid: true, result: '/path_params/.%2e%2e/download' }, - '["",""]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - '/path_params/./download\n' + - ' ^', - }, - '["","."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '/path_params/../download\n' + - ' ^^', - }, - }, - '["/path_params/","..","/download"]': { - '["","%2E"]': { valid: true, result: '/path_params/..%2E/download' }, - '["","x"]': { valid: true, result: '/path_params/..x/download' }, - '["",""]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '/path_params/../download\n' + - ' ^^', - }, - }, - }); - }); -}); - -describe('encodeURIPath', () => { - const testCases: string[] = [ - '', - // Every ASCII character - ...Array.from({ length: 0x7f }, (_, i) => String.fromCharCode(i)), - // Unicode BMP codepoint - 'å', - // Unicode supplementary codepoint - '😃', - ]; - - for (const param of testCases) { - test('properly encodes ' + inspect(param), () => { - const encoded = encodeURIPath(param); - const naiveEncoded = encodeURIComponent(param); - // we should never encode more characters than encodeURIComponent - expect(naiveEncoded.length).toBeGreaterThanOrEqual(encoded.length); - expect(decodeURIComponent(encoded)).toBe(param); - }); - } - - test("leaves ':' intact", () => { - expect(encodeURIPath(':')).toBe(':'); - }); - - test("leaves '@' intact", () => { - expect(encodeURIPath('@')).toBe('@'); - }); -}); diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts deleted file mode 100644 index f848797..0000000 --- a/tests/stringifyQuery.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { BrowserUse } from 'browser-use-sdk'; - -const { stringifyQuery } = BrowserUse.prototype as any; - -describe(stringifyQuery, () => { - for (const [input, expected] of [ - [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], - [{ a: null, b: false, c: undefined }, 'a=&b=false'], - [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], - [ - { 'a/b': 'c/d', 'e=f': 'g&h' }, - `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( - 'e=f', - )}=${encodeURIComponent('g&h')}`, - ], - ]) { - it(`${JSON.stringify(input)} -> ${expected}`, () => { - expect(stringifyQuery(input)).toEqual(expected); - }); - } - - for (const value of [[], {}, new Date()]) { - it(`${JSON.stringify(value)} -> `, () => { - expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); - }); - } -}); diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 0000000..10185ed --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": null, + "rootDir": "..", + "baseUrl": ".." + }, + "include": ["../src", "../tests"], + "exclude": [] +} diff --git a/tests/unit/fetcher/Fetcher.test.ts b/tests/unit/fetcher/Fetcher.test.ts new file mode 100644 index 0000000..f983f08 --- /dev/null +++ b/tests/unit/fetcher/Fetcher.test.ts @@ -0,0 +1,256 @@ +import fs from "fs"; +import stream from "stream"; +import { join } from "path"; + +import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; +import type { BinaryResponse } from "../../../src/core"; + +describe("Test fetcherImpl", () => { + it("should handle successful request", async () => { + const mockArgs: Fetcher.Args = { + url: "https://httpbin.org/post", + method: "POST", + headers: { "X-Test": "x-test-header" }, + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + responseType: "json", + }; + + global.fetch = jest.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + }), + ); + + const result = await fetcherImpl(mockArgs); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + + expect(global.fetch).toHaveBeenCalledWith( + "https://httpbin.org/post", + expect.objectContaining({ + method: "POST", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + body: JSON.stringify({ data: "test" }), + }), + ); + }); + + it("should send octet stream", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "POST", + headers: { "X-Test": "x-test-header" }, + contentType: "application/octet-stream", + requestType: "bytes", + responseType: "json", + body: fs.createReadStream(join(__dirname, "test-file.txt")), + }; + + global.fetch = jest.fn().mockResolvedValue( + new Response(JSON.stringify({ data: "test" }), { + status: 200, + statusText: "OK", + }), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "POST", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + body: expect.any(fs.ReadStream), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + }); + + it("should receive file as stream", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + responseType: "binary-response", + }; + + global.fetch = jest.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.stream).toBe("function"); + const stream = body.stream(); + expect(stream).toBeInstanceOf(ReadableStream); + const reader = stream.getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(value); + expect(streamContent).toBe("This is a test file!\n"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as blob", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + responseType: "binary-response", + }; + + global.fetch = jest.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.blob).toBe("function"); + const blob = await body.blob(); + expect(blob).toBeInstanceOf(Blob); + const reader = blob.stream().getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(value); + expect(streamContent).toBe("This is a test file!\n"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as arraybuffer", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + responseType: "binary-response", + }; + + global.fetch = jest.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.arrayBuffer).toBe("function"); + const arrayBuffer = await body.arrayBuffer(); + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(new Uint8Array(arrayBuffer)); + expect(streamContent).toBe("This is a test file!\n"); + expect(body.bodyUsed).toBe(true); + } + }); + + it("should receive file as bytes", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "GET", + headers: { "X-Test": "x-test-header" }, + responseType: "binary-response", + }; + + global.fetch = jest.fn().mockResolvedValue( + new Response( + stream.Readable.toWeb(fs.createReadStream(join(__dirname, "test-file.txt"))) as ReadableStream, + { + status: 200, + statusText: "OK", + }, + ), + ); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "GET", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + }), + ); + expect(result.ok).toBe(true); + if (result.ok) { + const body = result.body as BinaryResponse; + expect(body).toBeDefined(); + expect(body.bodyUsed).toBe(false); + expect(typeof body.bytes).toBe("function"); + if (!body.bytes) { + return; + } + const bytes = await body.bytes(); + expect(bytes).toBeInstanceOf(Uint8Array); + const decoder = new TextDecoder(); + const streamContent = decoder.decode(bytes); + expect(streamContent).toBe("This is a test file!\n"); + expect(body.bodyUsed).toBe(true); + } + }); +}); diff --git a/tests/unit/fetcher/HttpResponsePromise.test.ts b/tests/unit/fetcher/HttpResponsePromise.test.ts new file mode 100644 index 0000000..2216a33 --- /dev/null +++ b/tests/unit/fetcher/HttpResponsePromise.test.ts @@ -0,0 +1,143 @@ +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; + +import { HttpResponsePromise } from "../../../src/core/fetcher/HttpResponsePromise"; +import { RawResponse, WithRawResponse } from "../../../src/core/fetcher/RawResponse"; + +describe("HttpResponsePromise", () => { + const mockRawResponse: RawResponse = { + headers: new Headers(), + redirected: false, + status: 200, + statusText: "OK", + type: "basic" as ResponseType, + url: "https://example.com", + }; + const mockData = { id: "123", name: "test" }; + const mockWithRawResponse: WithRawResponse = { + data: mockData, + rawResponse: mockRawResponse, + }; + + describe("fromFunction", () => { + it("should create an HttpResponsePromise from a function", async () => { + const mockFn = jest + .fn<(arg1: string, arg2: string) => Promise>>() + .mockResolvedValue(mockWithRawResponse); + + const responsePromise = HttpResponsePromise.fromFunction(mockFn, "arg1", "arg2"); + + const result = await responsePromise; + expect(result).toEqual(mockData); + expect(mockFn).toHaveBeenCalledWith("arg1", "arg2"); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromPromise", () => { + it("should create an HttpResponsePromise from a promise", async () => { + const promise = Promise.resolve(mockWithRawResponse); + + const responsePromise = HttpResponsePromise.fromPromise(promise); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromExecutor", () => { + it("should create an HttpResponsePromise from an executor function", async () => { + const responsePromise = HttpResponsePromise.fromExecutor((resolve) => { + resolve(mockWithRawResponse); + }); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("fromResult", () => { + it("should create an HttpResponsePromise from a result", async () => { + const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + + const result = await responsePromise; + expect(result).toEqual(mockData); + + const resultWithRawResponse = await responsePromise.withRawResponse(); + expect(resultWithRawResponse).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); + + describe("Promise methods", () => { + let responsePromise: HttpResponsePromise; + + beforeEach(() => { + responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + }); + + it("should support then() method", async () => { + const result = await responsePromise.then((data) => ({ + ...data, + modified: true, + })); + + expect(result).toEqual({ + ...mockData, + modified: true, + }); + }); + + it("should support catch() method", async () => { + const errorResponsePromise = HttpResponsePromise.fromExecutor((_, reject) => { + reject(new Error("Test error")); + }); + + const catchSpy = jest.fn(); + await errorResponsePromise.catch(catchSpy); + + expect(catchSpy).toHaveBeenCalled(); + const error = catchSpy.mock.calls[0]?.[0]; + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe("Test error"); + }); + + it("should support finally() method", async () => { + const finallySpy = jest.fn(); + await responsePromise.finally(finallySpy); + + expect(finallySpy).toHaveBeenCalled(); + }); + }); + + describe("withRawResponse", () => { + it("should return both data and raw response", async () => { + const responsePromise = HttpResponsePromise.fromResult(mockWithRawResponse); + + const result = await responsePromise.withRawResponse(); + + expect(result).toEqual({ + data: mockData, + rawResponse: mockRawResponse, + }); + }); + }); +}); diff --git a/tests/unit/fetcher/RawResponse.test.ts b/tests/unit/fetcher/RawResponse.test.ts new file mode 100644 index 0000000..9ccd5e1 --- /dev/null +++ b/tests/unit/fetcher/RawResponse.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "@jest/globals"; + +import { toRawResponse } from "../../../src/core/fetcher/RawResponse"; + +describe("RawResponse", () => { + describe("toRawResponse", () => { + it("should convert Response to RawResponse by removing body, bodyUsed, and ok properties", () => { + const mockHeaders = new Headers({ "content-type": "application/json" }); + const mockResponse = { + body: "test body", + bodyUsed: false, + ok: true, + headers: mockHeaders, + redirected: false, + status: 200, + statusText: "OK", + type: "basic" as ResponseType, + url: "https://example.com", + }; + + const result = toRawResponse(mockResponse as unknown as Response); + + expect("body" in result).toBe(false); + expect("bodyUsed" in result).toBe(false); + expect("ok" in result).toBe(false); + expect(result.headers).toBe(mockHeaders); + expect(result.redirected).toBe(false); + expect(result.status).toBe(200); + expect(result.statusText).toBe("OK"); + expect(result.type).toBe("basic"); + expect(result.url).toBe("https://example.com"); + }); + }); +}); diff --git a/tests/unit/fetcher/createRequestUrl.test.ts b/tests/unit/fetcher/createRequestUrl.test.ts new file mode 100644 index 0000000..06e03b2 --- /dev/null +++ b/tests/unit/fetcher/createRequestUrl.test.ts @@ -0,0 +1,160 @@ +import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; + +describe("Test createRequestUrl", () => { + it("should return the base URL when no query parameters are provided", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl)).toBe(baseUrl); + }); + + it("should append simple query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { key: "value", another: "param" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?key=value&another=param"); + }); + + it("should handle array query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { items: ["a", "b", "c"] }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?items=a&items=b&items=c"); + }); + + it("should handle object query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { filter: { name: "John", age: 30 } }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30", + ); + }); + + it("should handle mixed types of query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + simple: "value", + array: ["x", "y"], + object: { key: "value" }, + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value", + ); + }); + + it("should handle empty query parameters object", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl, {})).toBe(baseUrl); + }); + + it("should encode special characters in query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { special: "a&b=c d" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?special=a%26b%3Dc%20d"); + }); + + // Additional tests for edge cases and different value types + it("should handle numeric values", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { count: 42, price: 19.99, active: 1, inactive: 0 }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?count=42&price=19.99&active=1&inactive=0", + ); + }); + + it("should handle boolean values", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { enabled: true, disabled: false }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?enabled=true&disabled=false"); + }); + + it("should handle null and undefined values", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + valid: "value", + nullValue: null, + undefinedValue: undefined, + emptyString: "", + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?valid=value&nullValue=&emptyString=", + ); + }); + + it("should handle deeply nested objects", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + user: { + profile: { + name: "John", + settings: { theme: "dark" }, + }, + }, + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", + ); + }); + + it("should handle arrays of objects", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + users: [ + { name: "John", age: 30 }, + { name: "Jane", age: 25 }, + ], + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?users%5Bname%5D=John&users%5Bage%5D=30&users%5Bname%5D=Jane&users%5Bage%5D=25", + ); + }); + + it("should handle mixed arrays", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + mixed: ["string", 42, true, { key: "value" }], + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?mixed=string&mixed=42&mixed=true&mixed%5Bkey%5D=value", + ); + }); + + it("should handle empty arrays", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { emptyArray: [] }; + expect(createRequestUrl(baseUrl, queryParams)).toBe(baseUrl); + }); + + it("should handle empty objects", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { emptyObject: {} }; + expect(createRequestUrl(baseUrl, queryParams)).toBe(baseUrl); + }); + + it("should handle special characters in keys", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { "key with spaces": "value", "key[with]brackets": "value" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?key%20with%20spaces=value&key%5Bwith%5Dbrackets=value", + ); + }); + + it("should handle URL with existing query parameters", () => { + const baseUrl = "https://api.example.com?existing=param"; + const queryParams = { new: "value" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?existing=param?new=value"); + }); + + it("should handle complex nested structures", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + ); + }); +}); diff --git a/tests/unit/fetcher/getRequestBody.test.ts b/tests/unit/fetcher/getRequestBody.test.ts new file mode 100644 index 0000000..e864c8b --- /dev/null +++ b/tests/unit/fetcher/getRequestBody.test.ts @@ -0,0 +1,65 @@ +import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; +import { RUNTIME } from "../../../src/core/runtime"; + +describe("Test getRequestBody", () => { + it("should stringify body if not FormData in Node environment", async () => { + if (RUNTIME.type === "node") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const formData = new FormData(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); + + it("should stringify body if not FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return the Uint8Array", async () => { + const input = new Uint8Array([1, 2, 3]); + const result = await getRequestBody({ + body: input, + type: "bytes", + }); + expect(result).toBe(input); + }); + + it("should return the input for content-type 'application/x-www-form-urlencoded'", async () => { + const input = "key=value&another=param"; + const result = await getRequestBody({ + body: input, + type: "other", + }); + expect(result).toBe(input); + }); + + it("should JSON stringify objects", async () => { + const input = { key: "value" }; + const result = await getRequestBody({ + body: input, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + }); +}); diff --git a/tests/unit/fetcher/getResponseBody.test.ts b/tests/unit/fetcher/getResponseBody.test.ts new file mode 100644 index 0000000..400782f --- /dev/null +++ b/tests/unit/fetcher/getResponseBody.test.ts @@ -0,0 +1,77 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; + +describe("Test getResponseBody", () => { + it("should handle blob response type", async () => { + const mockBlob = new Blob(["test"], { type: "text/plain" }); + const mockResponse = new Response(mockBlob); + const result = await getResponseBody(mockResponse, "blob"); + // @ts-expect-error + expect(result.constructor.name).toBe("Blob"); + }); + + it("should handle sse response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "sse"); + expect(result).toBe(mockStream); + } + }); + + it("should handle streaming response type", async () => { + // Create a ReadableStream with some test data + const encoder = new TextEncoder(); + const testData = "test stream data"; + const mockStream = new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(testData)); + controller.close(); + }, + }); + + const mockResponse = new Response(mockStream); + const result = (await getResponseBody(mockResponse, "streaming")) as ReadableStream; + + expect(result).toBeInstanceOf(ReadableStream); + + // Read and verify the stream content + const reader = result.getReader(); + const decoder = new TextDecoder(); + const { value } = await reader.read(); + const streamContent = decoder.decode(value); + expect(streamContent).toBe(testData); + }); + + it("should handle text response type", async () => { + const mockResponse = new Response("test text"); + const result = await getResponseBody(mockResponse, "text"); + expect(result).toBe("test text"); + }); + + it("should handle JSON response", async () => { + const mockJson = { key: "value" }; + const mockResponse = new Response(JSON.stringify(mockJson)); + const result = await getResponseBody(mockResponse); + expect(result).toEqual(mockJson); + }); + + it("should handle empty response", async () => { + const mockResponse = new Response(""); + const result = await getResponseBody(mockResponse); + expect(result).toBeUndefined(); + }); + + it("should handle non-JSON response", async () => { + const mockResponse = new Response("invalid json"); + const result = await getResponseBody(mockResponse); + expect(result).toEqual({ + ok: false, + error: { + reason: "non-json", + statusCode: 200, + rawBody: "invalid json", + }, + }); + }); +}); diff --git a/tests/unit/fetcher/makeRequest.test.ts b/tests/unit/fetcher/makeRequest.test.ts new file mode 100644 index 0000000..43ed9d1 --- /dev/null +++ b/tests/unit/fetcher/makeRequest.test.ts @@ -0,0 +1,53 @@ +import { makeRequest } from "../../../src/core/fetcher/makeRequest"; + +describe("Test makeRequest", () => { + const mockPostUrl = "https://httpbin.org/post"; + const mockGetUrl = "https://httpbin.org/get"; + const mockHeaders = { "Content-Type": "application/json" }; + const mockBody = JSON.stringify({ key: "value" }); + + let mockFetch: jest.Mock; + + beforeEach(() => { + mockFetch = jest.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); + }); + + it("should handle POST request correctly", async () => { + const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockPostUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "POST", + headers: mockHeaders, + body: mockBody, + credentials: undefined, + }), + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should handle GET request correctly", async () => { + const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockGetUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "GET", + headers: mockHeaders, + body: undefined, + credentials: undefined, + }), + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); +}); diff --git a/tests/unit/fetcher/requestWithRetries.test.ts b/tests/unit/fetcher/requestWithRetries.test.ts new file mode 100644 index 0000000..3cdaa40 --- /dev/null +++ b/tests/unit/fetcher/requestWithRetries.test.ts @@ -0,0 +1,132 @@ +import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; + +describe("requestWithRetries", () => { + let mockFetch: jest.Mock; + let originalMathRandom: typeof Math.random; + let setTimeoutSpy: jest.SpyInstance; + + beforeEach(() => { + mockFetch = jest.fn(); + originalMathRandom = Math.random; + + // Mock Math.random for consistent jitter + Math.random = jest.fn(() => 0.5); + + jest.useFakeTimers({ doNotFake: ["nextTick"] }); + }); + + afterEach(() => { + Math.random = originalMathRandom; + jest.clearAllMocks(); + jest.clearAllTimers(); + }); + + it("should retry on retryable status codes", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const retryableStatuses = [408, 429, 500, 502]; + let callCount = 0; + + mockFetch.mockImplementation(async () => { + if (callCount < retryableStatuses.length) { + return new Response("", { status: retryableStatuses[callCount++] }); + } + return new Response("", { status: 200 }); + }); + + const responsePromise = requestWithRetries(() => mockFetch(), retryableStatuses.length); + await jest.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(retryableStatuses.length + 1); + expect(response.status).toBe(200); + }); + + it("should respect maxRetries limit", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const maxRetries = 2; + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await jest.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + expect(response.status).toBe(500); + }); + + it("should not retry on success status codes", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + const successStatuses = [200, 201, 202]; + + for (const status of successStatuses) { + mockFetch.mockReset(); + setTimeoutSpy.mockClear(); + mockFetch.mockResolvedValueOnce(new Response("", { status })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + await jest.runAllTimersAsync(); + await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(setTimeoutSpy).not.toHaveBeenCalled(); + } + }); + + it("should apply correct exponential backoff with jitter", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + const maxRetries = 3; + const expectedDelays = [1000, 2000, 4000]; + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await jest.runAllTimersAsync(); + await responsePromise; + + // Verify setTimeout calls + expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length); + + expectedDelays.forEach((delay, index) => { + expect(setTimeoutSpy).toHaveBeenNthCalledWith(index + 1, expect.any(Function), delay); + }); + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + }); + + it("should handle concurrent retries independently", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const promise1 = requestWithRetries(() => mockFetch(), 1); + const promise2 = requestWithRetries(() => mockFetch(), 1); + + await jest.runAllTimersAsync(); + const [response1, response2] = await Promise.all([promise1, promise2]); + + expect(response1.status).toBe(200); + expect(response2.status).toBe(200); + }); +}); diff --git a/tests/unit/fetcher/signals.test.ts b/tests/unit/fetcher/signals.test.ts new file mode 100644 index 0000000..9cabfa0 --- /dev/null +++ b/tests/unit/fetcher/signals.test.ts @@ -0,0 +1,69 @@ +import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; + +describe("Test getTimeoutSignal", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("should return an object with signal and abortId", () => { + const { signal, abortId } = getTimeoutSignal(1000); + + expect(signal).toBeDefined(); + expect(abortId).toBeDefined(); + expect(signal).toBeInstanceOf(AbortSignal); + expect(signal.aborted).toBe(false); + }); + + it("should create a signal that aborts after the specified timeout", () => { + const timeoutMs = 5000; + const { signal } = getTimeoutSignal(timeoutMs); + + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(timeoutMs - 1); + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(1); + expect(signal.aborted).toBe(true); + }); +}); + +describe("Test anySignal", () => { + it("should return an AbortSignal", () => { + const signal = anySignal(new AbortController().signal); + expect(signal).toBeInstanceOf(AbortSignal); + }); + + it("should abort when any of the input signals is aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal(controller1.signal, controller2.signal); + + expect(signal.aborted).toBe(false); + controller1.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should handle an array of signals", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal([controller1.signal, controller2.signal]); + + expect(signal.aborted).toBe(false); + controller2.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should abort immediately if one of the input signals is already aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + controller1.abort(); + + const signal = anySignal(controller1.signal, controller2.signal); + expect(signal.aborted).toBe(true); + }); +}); diff --git a/tests/unit/fetcher/test-file.txt b/tests/unit/fetcher/test-file.txt new file mode 100644 index 0000000..c66d471 --- /dev/null +++ b/tests/unit/fetcher/test-file.txt @@ -0,0 +1 @@ +This is a test file! diff --git a/tests/unit/url/join.test.ts b/tests/unit/url/join.test.ts new file mode 100644 index 0000000..984cfe6 --- /dev/null +++ b/tests/unit/url/join.test.ts @@ -0,0 +1,120 @@ +import { join } from "../../../src/core/url/index"; + +describe("join", () => { + describe("basic functionality", () => { + it("should return empty string for empty base", () => { + expect(join("")).toBe(""); + expect(join("", "path")).toBe(""); + }); + + it("should handle single segment", () => { + expect(join("base", "segment")).toBe("base/segment"); + expect(join("base/", "segment")).toBe("base/segment"); + expect(join("base", "/segment")).toBe("base/segment"); + expect(join("base/", "/segment")).toBe("base/segment"); + }); + + it("should handle multiple segments", () => { + expect(join("base", "path1", "path2", "path3")).toBe("base/path1/path2/path3"); + expect(join("base/", "/path1/", "/path2/", "/path3/")).toBe("base/path1/path2/path3/"); + }); + }); + + describe("URL handling", () => { + it("should handle absolute URLs", () => { + expect(join("https://example.com", "api", "v1")).toBe("https://example.com/api/v1"); + expect(join("https://example.com/", "/api/", "/v1/")).toBe("https://example.com/api/v1/"); + expect(join("https://example.com/base", "api", "v1")).toBe("https://example.com/base/api/v1"); + }); + + it("should preserve URL query parameters and fragments", () => { + expect(join("https://example.com?query=1", "api")).toBe("https://example.com/api?query=1"); + expect(join("https://example.com#fragment", "api")).toBe("https://example.com/api#fragment"); + expect(join("https://example.com?query=1#fragment", "api")).toBe( + "https://example.com/api?query=1#fragment", + ); + }); + + it("should handle different protocols", () => { + expect(join("http://example.com", "api")).toBe("http://example.com/api"); + expect(join("ftp://example.com", "files")).toBe("ftp://example.com/files"); + expect(join("ws://example.com", "socket")).toBe("ws://example.com/socket"); + }); + + it("should fallback to path joining for malformed URLs", () => { + expect(join("not-a-url://", "path")).toBe("not-a-url:///path"); + }); + }); + + describe("edge cases", () => { + it("should handle empty segments", () => { + expect(join("base", "", "path")).toBe("base/path"); + expect(join("base", null as any, "path")).toBe("base/path"); + expect(join("base", undefined as any, "path")).toBe("base/path"); + }); + + it("should handle segments with only slashes", () => { + expect(join("base", "/", "path")).toBe("base/path"); + expect(join("base", "//", "path")).toBe("base/path"); + }); + + it("should handle base paths with trailing slashes", () => { + expect(join("base/", "path")).toBe("base/path"); + }); + + it("should handle complex nested paths", () => { + expect(join("api/v1/", "/users/", "/123/", "/profile")).toBe("api/v1/users/123/profile"); + }); + }); + + describe("real-world scenarios", () => { + it("should handle API endpoint construction", () => { + const baseUrl = "https://api.example.com/v1"; + expect(join(baseUrl, "users", "123", "posts")).toBe("https://api.example.com/v1/users/123/posts"); + }); + + it("should handle file path construction", () => { + expect(join("/var/www", "html", "assets", "images")).toBe("/var/www/html/assets/images"); + }); + + it("should handle relative path construction", () => { + expect(join("../parent", "child", "grandchild")).toBe("../parent/child/grandchild"); + }); + + it("should handle Windows-style paths", () => { + expect(join("C:\\Users", "Documents", "file.txt")).toBe("C:\\Users/Documents/file.txt"); + }); + }); + + describe("performance scenarios", () => { + it("should handle many segments efficiently", () => { + const segments = Array(100).fill("segment"); + const result = join("base", ...segments); + expect(result).toBe("base/" + segments.join("/")); + }); + + it("should handle long URLs", () => { + const longPath = "a".repeat(1000); + expect(join("https://example.com", longPath)).toBe(`https://example.com/${longPath}`); + }); + }); + + describe("trailing slash preservation", () => { + it("should preserve trailing slash on final result when base has trailing slash and no segments", () => { + expect(join("https://api.example.com/")).toBe("https://api.example.com/"); + expect(join("https://api.example.com/v1/")).toBe("https://api.example.com/v1/"); + }); + + it("should preserve trailing slash when last segment has trailing slash", () => { + expect(join("https://api.example.com", "users/")).toBe("https://api.example.com/users/"); + expect(join("api/v1", "users/")).toBe("api/v1/users/"); + }); + + it("should preserve trailing slash with multiple segments where last has trailing slash", () => { + expect(join("https://api.example.com", "v1", "collections/")).toBe( + "https://api.example.com/v1/collections/", + ); + expect(join("base", "path1", "path2/")).toBe("base/path1/path2/"); + }); + }); +}); diff --git a/tests/unit/url/qs.test.ts b/tests/unit/url/qs.test.ts new file mode 100644 index 0000000..80e7e04 --- /dev/null +++ b/tests/unit/url/qs.test.ts @@ -0,0 +1,187 @@ +import { toQueryString } from "../../../src/core/url/index"; + +describe("Test qs toQueryString", () => { + describe("Basic functionality", () => { + it("should return empty string for null/undefined", () => { + expect(toQueryString(null)).toBe(""); + expect(toQueryString(undefined)).toBe(""); + }); + + it("should return empty string for primitive values", () => { + expect(toQueryString("hello")).toBe(""); + expect(toQueryString(42)).toBe(""); + expect(toQueryString(true)).toBe(""); + expect(toQueryString(false)).toBe(""); + }); + + it("should handle empty objects", () => { + expect(toQueryString({})).toBe(""); + }); + + it("should handle simple key-value pairs", () => { + const obj = { name: "John", age: 30 }; + expect(toQueryString(obj)).toBe("name=John&age=30"); + }); + }); + + describe("Array handling", () => { + it("should handle arrays with indices format (default)", () => { + const obj = { items: ["a", "b", "c"] }; + expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=b&items%5B2%5D=c"); + }); + + it("should handle arrays with repeat format", () => { + const obj = { items: ["a", "b", "c"] }; + expect(toQueryString(obj, { arrayFormat: "repeat" })).toBe("items=a&items=b&items=c"); + }); + + it("should handle empty arrays", () => { + const obj = { items: [] }; + expect(toQueryString(obj)).toBe(""); + }); + + it("should handle arrays with mixed types", () => { + const obj = { mixed: ["string", 42, true, false] }; + expect(toQueryString(obj)).toBe("mixed%5B0%5D=string&mixed%5B1%5D=42&mixed%5B2%5D=true&mixed%5B3%5D=false"); + }); + + it("should handle arrays with objects", () => { + const obj = { users: [{ name: "John" }, { name: "Jane" }] }; + expect(toQueryString(obj)).toBe("users%5B0%5D%5Bname%5D=John&users%5B1%5D%5Bname%5D=Jane"); + }); + + it("should handle arrays with objects in repeat format", () => { + const obj = { users: [{ name: "John" }, { name: "Jane" }] }; + expect(toQueryString(obj, { arrayFormat: "repeat" })).toBe("users%5Bname%5D=John&users%5Bname%5D=Jane"); + }); + }); + + describe("Nested objects", () => { + it("should handle nested objects", () => { + const obj = { user: { name: "John", age: 30 } }; + expect(toQueryString(obj)).toBe("user%5Bname%5D=John&user%5Bage%5D=30"); + }); + + it("should handle deeply nested objects", () => { + const obj = { user: { profile: { name: "John", settings: { theme: "dark" } } } }; + expect(toQueryString(obj)).toBe( + "user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark", + ); + }); + + it("should handle empty nested objects", () => { + const obj = { user: {} }; + expect(toQueryString(obj)).toBe(""); + }); + }); + + describe("Encoding", () => { + it("should encode by default", () => { + const obj = { name: "John Doe", email: "john@example.com" }; + expect(toQueryString(obj)).toBe("name=John%20Doe&email=john%40example.com"); + }); + + it("should not encode when encode is false", () => { + const obj = { name: "John Doe", email: "john@example.com" }; + expect(toQueryString(obj, { encode: false })).toBe("name=John Doe&email=john@example.com"); + }); + + it("should encode special characters in keys", () => { + const obj = { "user name": "John", "email[primary]": "john@example.com" }; + expect(toQueryString(obj)).toBe("user%20name=John&email%5Bprimary%5D=john%40example.com"); + }); + + it("should not encode special characters in keys when encode is false", () => { + const obj = { "user name": "John", "email[primary]": "john@example.com" }; + expect(toQueryString(obj, { encode: false })).toBe("user name=John&email[primary]=john@example.com"); + }); + }); + + describe("Mixed scenarios", () => { + it("should handle complex nested structures", () => { + const obj = { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }; + expect(toQueryString(obj)).toBe( + "filters%5Bstatus%5D%5B0%5D=active&filters%5Bstatus%5D%5B1%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D%5B0%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D%5B1%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + ); + }); + + it("should handle complex nested structures with repeat format", () => { + const obj = { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + }; + expect(toQueryString(obj, { arrayFormat: "repeat" })).toBe( + "filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc", + ); + }); + + it("should handle arrays with null/undefined values", () => { + const obj = { items: ["a", null, "c", undefined, "e"] }; + expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e"); + }); + + it("should handle objects with null/undefined values", () => { + const obj = { name: "John", age: null, email: undefined, active: true }; + expect(toQueryString(obj)).toBe("name=John&age=&active=true"); + }); + }); + + describe("Edge cases", () => { + it("should handle numeric keys", () => { + const obj = { "0": "zero", "1": "one" }; + expect(toQueryString(obj)).toBe("0=zero&1=one"); + }); + + it("should handle boolean values in objects", () => { + const obj = { enabled: true, disabled: false }; + expect(toQueryString(obj)).toBe("enabled=true&disabled=false"); + }); + + it("should handle empty strings", () => { + const obj = { name: "", description: "test" }; + expect(toQueryString(obj)).toBe("name=&description=test"); + }); + + it("should handle zero values", () => { + const obj = { count: 0, price: 0.0 }; + expect(toQueryString(obj)).toBe("count=0&price=0"); + }); + + it("should handle arrays with empty strings", () => { + const obj = { items: ["a", "", "c"] }; + expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c"); + }); + }); + + describe("Options combinations", () => { + it("should respect both arrayFormat and encode options", () => { + const obj = { items: ["a & b", "c & d"] }; + expect(toQueryString(obj, { arrayFormat: "repeat", encode: false })).toBe("items=a & b&items=c & d"); + }); + + it("should use default options when none provided", () => { + const obj = { items: ["a", "b"] }; + expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=b"); + }); + + it("should merge provided options with defaults", () => { + const obj = { items: ["a", "b"], name: "John Doe" }; + expect(toQueryString(obj, { encode: false })).toBe("items[0]=a&items[1]=b&name=John Doe"); + }); + }); +}); diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts deleted file mode 100644 index 29e1a46..0000000 --- a/tests/uploads.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import fs from 'fs'; -import type { ResponseLike } from 'browser-use-sdk/internal/to-file'; -import { toFile } from 'browser-use-sdk/core/uploads'; -import { File } from 'node:buffer'; - -class MyClass { - name: string = 'foo'; -} - -function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { - return { - url, - blob: async () => content || new Blob([]), - }; -} - -describe('toFile', () => { - it('throws a helpful error for mismatched types', async () => { - await expect( - // @ts-expect-error intentionally mismatched type - toFile({ foo: 'string' }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, - ); - - await expect( - // @ts-expect-error intentionally mismatched type - toFile(new MyClass()), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, - ); - }); - - it('disallows string at the type-level', async () => { - // @ts-expect-error we intentionally do not type support for `string` - // to help people avoid passing a file path - const file = await toFile('contents'); - expect(file.text()).resolves.toEqual('contents'); - }); - - it('extracts a file name from a Response', async () => { - const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); - const file = await toFile(response); - expect(file.name).toEqual('audio.mp3'); - }); - - it('extracts a file name from a File', async () => { - const input = new File(['foo'], 'input.jsonl'); - const file = await toFile(input); - expect(file.name).toEqual('input.jsonl'); - }); - - it('extracts a file name from a ReadStream', async () => { - const input = fs.createReadStream('tests/uploads.test.ts'); - const file = await toFile(input); - expect(file.name).toEqual('uploads.test.ts'); - }); - - it('does not copy File objects', async () => { - const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); - const file = await toFile(input); - expect(file).toBe(input); - expect(file.name).toEqual('input.jsonl'); - expect(file.type).toBe('jsonl'); - }); - - it('is assignable to File and Blob', async () => { - const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); - const result = await toFile(input); - const file: File = result; - const blob: Blob = result; - (void file, blob); - }); -}); - -describe('missing File error message', () => { - let prevGlobalFile: unknown; - let prevNodeFile: unknown; - beforeEach(() => { - // The file shim captures the global File object when it's first imported. - // Reset modules before each test so we can test the error thrown when it's undefined. - jest.resetModules(); - const buffer = require('node:buffer'); - // @ts-ignore - prevGlobalFile = globalThis.File; - prevNodeFile = buffer.File; - // @ts-ignore - globalThis.File = undefined; - buffer.File = undefined; - }); - afterEach(() => { - // Clean up - // @ts-ignore - globalThis.File = prevGlobalFile; - require('node:buffer').File = prevNodeFile; - jest.resetModules(); - }); - - test('is thrown', async () => { - const uploads = await import('browser-use-sdk/core/uploads'); - await expect( - uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })), - ).rejects.toMatchInlineSnapshot( - `[Error: \`File\` is not defined as a global, which is required for file uploads.]`, - ); - }); -}); diff --git a/tests/wire/.gitkeep b/tests/wire/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/wire/account.test.ts b/tests/wire/account.test.ts new file mode 100644 index 0000000..71ab9fe --- /dev/null +++ b/tests/wire/account.test.ts @@ -0,0 +1,31 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import { mockServerPool } from "../mock-server/MockServerPool"; +import { BrowserUseClient } from "../../src/Client"; + +describe("Account", () => { + test("getAccountMe", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + monthlyCreditsBalanceUsd: 1.1, + additionalCreditsBalanceUsd: 1.1, + email: "email", + name: "name", + signedUpAt: "2024-01-15T09:30:00Z", + }; + server.mockEndpoint().get("/account/me").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.account.getAccountMe(); + expect(response).toEqual({ + monthlyCreditsBalanceUsd: 1.1, + additionalCreditsBalanceUsd: 1.1, + email: "email", + name: "name", + signedUpAt: "2024-01-15T09:30:00Z", + }); + }); +}); diff --git a/tests/wire/files.test.ts b/tests/wire/files.test.ts new file mode 100644 index 0000000..620008b --- /dev/null +++ b/tests/wire/files.test.ts @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import { mockServerPool } from "../mock-server/MockServerPool"; +import { BrowserUseClient } from "../../src/Client"; + +describe("Files", () => { + test("userUploadFilePresignedUrl", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + const rawRequestBody = { fileName: "fileName", contentType: "image/jpg", sizeBytes: 1 }; + const rawResponseBody = { + url: "url", + method: "POST", + fields: { key: "value" }, + fileName: "fileName", + expiresIn: 1, + }; + server + .mockEndpoint() + .post("/files/sessions/session_id/presigned-url") + .jsonBody(rawRequestBody) + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.files.userUploadFilePresignedUrl("session_id", { + fileName: "fileName", + contentType: "image/jpg", + sizeBytes: 1, + }); + expect(response).toEqual({ + url: "url", + method: "POST", + fields: { + key: "value", + }, + fileName: "fileName", + expiresIn: 1, + }); + }); + + test("getTaskUserUploadedFilePresignedUrl", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { id: "id", fileName: "fileName", downloadUrl: "downloadUrl" }; + server + .mockEndpoint() + .get("/files/tasks/task_id/uploaded-files/file_id") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.files.getTaskUserUploadedFilePresignedUrl("task_id", "file_id"); + expect(response).toEqual({ + id: "id", + fileName: "fileName", + downloadUrl: "downloadUrl", + }); + }); + + test("getTaskOutputFilePresignedUrl", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { id: "id", fileName: "fileName", downloadUrl: "downloadUrl" }; + server + .mockEndpoint() + .get("/files/tasks/task_id/output-files/file_id") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.files.getTaskOutputFilePresignedUrl("task_id", "file_id"); + expect(response).toEqual({ + id: "id", + fileName: "fileName", + downloadUrl: "downloadUrl", + }); + }); +}); diff --git a/tests/wire/profiles.test.ts b/tests/wire/profiles.test.ts new file mode 100644 index 0000000..32334c9 --- /dev/null +++ b/tests/wire/profiles.test.ts @@ -0,0 +1,101 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import { mockServerPool } from "../mock-server/MockServerPool"; +import { BrowserUseClient } from "../../src/Client"; + +describe("Profiles", () => { + test("listProfiles", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + items: [ + { + id: "id", + lastUsedAt: "2024-01-15T09:30:00Z", + createdAt: "2024-01-15T09:30:00Z", + updatedAt: "2024-01-15T09:30:00Z", + }, + ], + totalItems: 1, + pageNumber: 1, + pageSize: 1, + }; + server.mockEndpoint().get("/profiles").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.profiles.listProfiles(); + expect(response).toEqual({ + items: [ + { + id: "id", + lastUsedAt: "2024-01-15T09:30:00Z", + createdAt: "2024-01-15T09:30:00Z", + updatedAt: "2024-01-15T09:30:00Z", + }, + ], + totalItems: 1, + pageNumber: 1, + pageSize: 1, + }); + }); + + test("createProfile", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + id: "id", + lastUsedAt: "2024-01-15T09:30:00Z", + createdAt: "2024-01-15T09:30:00Z", + updatedAt: "2024-01-15T09:30:00Z", + }; + server.mockEndpoint().post("/profiles").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.profiles.createProfile(); + expect(response).toEqual({ + id: "id", + lastUsedAt: "2024-01-15T09:30:00Z", + createdAt: "2024-01-15T09:30:00Z", + updatedAt: "2024-01-15T09:30:00Z", + }); + }); + + test("getProfile", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + id: "id", + lastUsedAt: "2024-01-15T09:30:00Z", + createdAt: "2024-01-15T09:30:00Z", + updatedAt: "2024-01-15T09:30:00Z", + }; + server + .mockEndpoint() + .get("/profiles/profile_id") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.profiles.getProfile("profile_id"); + expect(response).toEqual({ + id: "id", + lastUsedAt: "2024-01-15T09:30:00Z", + createdAt: "2024-01-15T09:30:00Z", + updatedAt: "2024-01-15T09:30:00Z", + }); + }); + + test("deleteBrowserProfile", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + server.mockEndpoint().delete("/profiles/profile_id").respondWith().statusCode(200).build(); + + const response = await client.profiles.deleteBrowserProfile("profile_id"); + expect(response).toEqual(undefined); + }); +}); diff --git a/tests/wire/sessions.test.ts b/tests/wire/sessions.test.ts new file mode 100644 index 0000000..be68f5c --- /dev/null +++ b/tests/wire/sessions.test.ts @@ -0,0 +1,254 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import { mockServerPool } from "../mock-server/MockServerPool"; +import { BrowserUseClient } from "../../src/Client"; + +describe("Sessions", () => { + test("listSessions", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + items: [ + { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + }, + ], + totalItems: 1, + pageNumber: 1, + pageSize: 1, + }; + server.mockEndpoint().get("/sessions").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.sessions.listSessions(); + expect(response).toEqual({ + items: [ + { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + }, + ], + totalItems: 1, + pageNumber: 1, + pageSize: 1, + }); + }); + + test("getSession", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + tasks: [ + { + id: "id", + sessionId: "sessionId", + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { key: "value" }, + isScheduled: true, + output: "output", + browserUseVersion: "browserUseVersion", + isSuccess: true, + }, + ], + recordUrl: "recordUrl", + publicShareUrl: "publicShareUrl", + }; + server + .mockEndpoint() + .get("/sessions/session_id") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.sessions.getSession("session_id"); + expect(response).toEqual({ + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + tasks: [ + { + id: "id", + sessionId: "sessionId", + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { + key: "value", + }, + isScheduled: true, + output: "output", + browserUseVersion: "browserUseVersion", + isSuccess: true, + }, + ], + recordUrl: "recordUrl", + publicShareUrl: "publicShareUrl", + }); + }); + + test("deleteSession", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + server.mockEndpoint().delete("/sessions/session_id").respondWith().statusCode(200).build(); + + const response = await client.sessions.deleteSession("session_id"); + expect(response).toEqual(undefined); + }); + + test("updateSession", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + const rawRequestBody = { action: "stop" }; + const rawResponseBody = { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + tasks: [ + { + id: "id", + sessionId: "sessionId", + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { key: "value" }, + isScheduled: true, + output: "output", + browserUseVersion: "browserUseVersion", + isSuccess: true, + }, + ], + recordUrl: "recordUrl", + publicShareUrl: "publicShareUrl", + }; + server + .mockEndpoint() + .patch("/sessions/session_id") + .jsonBody(rawRequestBody) + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.sessions.updateSession("session_id", {}); + expect(response).toEqual({ + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + tasks: [ + { + id: "id", + sessionId: "sessionId", + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { + key: "value", + }, + isScheduled: true, + output: "output", + browserUseVersion: "browserUseVersion", + isSuccess: true, + }, + ], + recordUrl: "recordUrl", + publicShareUrl: "publicShareUrl", + }); + }); + + test("getSessionPublicShare", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + shareToken: "shareToken", + shareUrl: "shareUrl", + viewCount: 1, + lastViewedAt: "2024-01-15T09:30:00Z", + }; + server + .mockEndpoint() + .get("/sessions/session_id/public-share") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.sessions.getSessionPublicShare("session_id"); + expect(response).toEqual({ + shareToken: "shareToken", + shareUrl: "shareUrl", + viewCount: 1, + lastViewedAt: "2024-01-15T09:30:00Z", + }); + }); + + test("createSessionPublicShare", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + shareToken: "shareToken", + shareUrl: "shareUrl", + viewCount: 1, + lastViewedAt: "2024-01-15T09:30:00Z", + }; + server + .mockEndpoint() + .post("/sessions/session_id/public-share") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.sessions.createSessionPublicShare("session_id"); + expect(response).toEqual({ + shareToken: "shareToken", + shareUrl: "shareUrl", + viewCount: 1, + lastViewedAt: "2024-01-15T09:30:00Z", + }); + }); + + test("deleteSessionPublicShare", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + server.mockEndpoint().delete("/sessions/session_id/public-share").respondWith().statusCode(200).build(); + + const response = await client.sessions.deleteSessionPublicShare("session_id"); + expect(response).toEqual(undefined); + }); +}); diff --git a/tests/wire/tasks.test.ts b/tests/wire/tasks.test.ts new file mode 100644 index 0000000..4b7add6 --- /dev/null +++ b/tests/wire/tasks.test.ts @@ -0,0 +1,290 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import { mockServerPool } from "../mock-server/MockServerPool"; +import { BrowserUseClient } from "../../src/Client"; + +describe("Tasks", () => { + test("listTasks", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + items: [ + { + id: "id", + sessionId: "sessionId", + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { key: "value" }, + isScheduled: true, + output: "output", + browserUseVersion: "browserUseVersion", + isSuccess: true, + }, + ], + totalItems: 1, + pageNumber: 1, + pageSize: 1, + }; + server.mockEndpoint().get("/tasks").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.tasks.listTasks(); + expect(response).toEqual({ + items: [ + { + id: "id", + sessionId: "sessionId", + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { + key: "value", + }, + isScheduled: true, + output: "output", + browserUseVersion: "browserUseVersion", + isSuccess: true, + }, + ], + totalItems: 1, + pageNumber: 1, + pageSize: 1, + }); + }); + + test("createTask", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + const rawRequestBody = { task: "task" }; + const rawResponseBody = { id: "id", sessionId: "sessionId" }; + server + .mockEndpoint() + .post("/tasks") + .jsonBody(rawRequestBody) + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.tasks.createTask({ + task: "task", + }); + expect(response).toEqual({ + id: "id", + sessionId: "sessionId", + }); + }); + + test("getTask", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { + id: "id", + sessionId: "sessionId", + session: { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + }, + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { key: "value" }, + isScheduled: true, + steps: [ + { + number: 1, + memory: "memory", + evaluationPreviousGoal: "evaluationPreviousGoal", + nextGoal: "nextGoal", + url: "url", + screenshotUrl: "screenshotUrl", + actions: ["actions"], + }, + ], + output: "output", + userUploadedFiles: [{ id: "id", fileName: "fileName" }], + outputFiles: [{ id: "id", fileName: "fileName" }], + browserUseVersion: "browserUseVersion", + isSuccess: true, + }; + server.mockEndpoint().get("/tasks/task_id").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.tasks.getTask("task_id"); + expect(response).toEqual({ + id: "id", + sessionId: "sessionId", + session: { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + }, + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { + key: "value", + }, + isScheduled: true, + steps: [ + { + number: 1, + memory: "memory", + evaluationPreviousGoal: "evaluationPreviousGoal", + nextGoal: "nextGoal", + url: "url", + screenshotUrl: "screenshotUrl", + actions: ["actions"], + }, + ], + output: "output", + userUploadedFiles: [ + { + id: "id", + fileName: "fileName", + }, + ], + outputFiles: [ + { + id: "id", + fileName: "fileName", + }, + ], + browserUseVersion: "browserUseVersion", + isSuccess: true, + }); + }); + + test("updateTask", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + const rawRequestBody = { action: "stop" }; + const rawResponseBody = { + id: "id", + sessionId: "sessionId", + session: { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + }, + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { key: "value" }, + isScheduled: true, + steps: [ + { + number: 1, + memory: "memory", + evaluationPreviousGoal: "evaluationPreviousGoal", + nextGoal: "nextGoal", + url: "url", + screenshotUrl: "screenshotUrl", + actions: ["actions"], + }, + ], + output: "output", + userUploadedFiles: [{ id: "id", fileName: "fileName" }], + outputFiles: [{ id: "id", fileName: "fileName" }], + browserUseVersion: "browserUseVersion", + isSuccess: true, + }; + server + .mockEndpoint() + .patch("/tasks/task_id") + .jsonBody(rawRequestBody) + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.tasks.updateTask("task_id", { + action: "stop", + }); + expect(response).toEqual({ + id: "id", + sessionId: "sessionId", + session: { + id: "id", + status: "active", + liveUrl: "liveUrl", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + }, + llm: "llm", + task: "task", + status: "started", + startedAt: "2024-01-15T09:30:00Z", + finishedAt: "2024-01-15T09:30:00Z", + metadata: { + key: "value", + }, + isScheduled: true, + steps: [ + { + number: 1, + memory: "memory", + evaluationPreviousGoal: "evaluationPreviousGoal", + nextGoal: "nextGoal", + url: "url", + screenshotUrl: "screenshotUrl", + actions: ["actions"], + }, + ], + output: "output", + userUploadedFiles: [ + { + id: "id", + fileName: "fileName", + }, + ], + outputFiles: [ + { + id: "id", + fileName: "fileName", + }, + ], + browserUseVersion: "browserUseVersion", + isSuccess: true, + }); + }); + + test("getTaskLogs", async () => { + const server = mockServerPool.createServer(); + const client = new BrowserUseClient({ apiKey: "test", environment: server.baseUrl }); + + const rawResponseBody = { downloadUrl: "downloadUrl" }; + server + .mockEndpoint() + .get("/tasks/task_id/logs") + .respondWith() + .statusCode(200) + .jsonBody(rawResponseBody) + .build(); + + const response = await client.tasks.getTaskLogs("task_id"); + expect(response).toEqual({ + downloadUrl: "downloadUrl", + }); + }); +}); diff --git a/tsc-multi.json b/tsc-multi.json deleted file mode 100644 index 384ddac..0000000 --- a/tsc-multi.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "targets": [ - { - "extname": ".js", - "module": "commonjs", - "shareHelpers": "internal/tslib.js" - }, - { - "extname": ".mjs", - "module": "esnext", - "shareHelpers": "internal/tslib.mjs" - } - ], - "projects": ["tsconfig.build.json"] -} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..c75083d --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "extendedDiagnostics": true, + "strict": true, + "target": "ES6", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "baseUrl": "src" + }, + "include": ["src"], + "exclude": [] +} diff --git a/tsconfig.build.json b/tsconfig.build.json deleted file mode 100644 index 6b4329f..0000000 --- a/tsconfig.build.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["dist/src"], - "exclude": [], - "compilerOptions": { - "rootDir": "./dist/src", - "paths": { - "browser-use-sdk/*": ["dist/src/*"], - "browser-use-sdk": ["dist/src/index.ts"] - }, - "noEmit": false, - "declaration": true, - "declarationMap": true, - "outDir": "dist", - "pretty": true, - "sourceMap": true - } -} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..5c11446 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "dist/cjs" + }, + "include": ["src"], + "exclude": [] +} diff --git a/tsconfig.deno.json b/tsconfig.deno.json deleted file mode 100644 index 849e070..0000000 --- a/tsconfig.deno.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["dist-deno"], - "exclude": [], - "compilerOptions": { - "rootDir": "./dist-deno", - "lib": ["es2020", "DOM"], - "noEmit": true, - "declaration": true, - "declarationMap": true, - "outDir": "dist-deno", - "pretty": true, - "sourceMap": true - } -} diff --git a/tsconfig.dist-src.json b/tsconfig.dist-src.json deleted file mode 100644 index c550e29..0000000 --- a/tsconfig.dist-src.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - // this config is included in the published src directory to prevent TS errors - // from appearing when users go to source, and VSCode opens the source .ts file - // via declaration maps - "include": ["index.ts"], - "compilerOptions": { - "target": "ES2015", - "lib": ["DOM", "DOM.Iterable", "ES2018"], - "moduleResolution": "node" - } -} diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 0000000..95a5eb7 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/esm" + }, + "include": ["src"], + "exclude": [] +} diff --git a/tsconfig.json b/tsconfig.json index 741d622..d77fdf0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,38 +1,3 @@ { - "include": ["src", "tests", "examples"], - "exclude": [], - "compilerOptions": { - "target": "es2020", - "lib": ["es2020"], - "module": "commonjs", - "moduleResolution": "node", - "esModuleInterop": true, - "baseUrl": "./", - "paths": { - "browser-use-sdk/*": ["src/*"], - "browser-use-sdk": ["src/index.ts"] - }, - "noEmit": true, - - "resolveJsonModule": true, - - "forceConsistentCasingInFileNames": true, - - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "noImplicitReturns": true, - "alwaysStrict": true, - "exactOptionalPropertyTypes": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "isolatedModules": false, - - "skipLibCheck": true - } + "extends": "./tsconfig.cjs.json" } diff --git a/yarn.lock b/yarn.lock index 12eb306..937b379 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,38 +10,6 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@andrewbranch/untar.js@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" - integrity sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw== - -"@arethetypeswrong/cli@^0.17.0": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@arethetypeswrong/cli/-/cli-0.17.4.tgz#45405f75081710c1cf0dab6edb4320fd75d215c8" - integrity sha512-AeiKxtf67XD/NdOqXgBOE5TZWH3EOCt+0GkbUpekOzngc+Q/cRZ5azjWyMxISxxfp0EItgm5NoSld9p7BAA5xQ== - dependencies: - "@arethetypeswrong/core" "0.17.4" - chalk "^4.1.2" - cli-table3 "^0.6.3" - commander "^10.0.1" - marked "^9.1.2" - marked-terminal "^7.1.0" - semver "^7.5.4" - -"@arethetypeswrong/core@0.17.4": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@arethetypeswrong/core/-/core-0.17.4.tgz#f3f85aa8bbcca6d215938580165e47b2244c7f4c" - integrity sha512-Izvir8iIoU+X4SKtDAa5kpb+9cpifclzsbA8x/AZY0k0gIfXYQ1fa1B6Epfe6vNA2YfDX8VtrZFgvnXB6aPEoQ== - dependencies: - "@andrewbranch/untar.js" "^1.0.3" - "@loaderkit/resolve" "^1.0.2" - cjs-module-lexer "^1.2.3" - fflate "^0.8.2" - lru-cache "^10.4.3" - semver "^7.5.4" - typescript "5.6.1-rc" - validate-npm-package-name "^5.0.0" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" @@ -310,136 +278,59 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@braidai/lang@^1.0.0": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@braidai/lang/-/lang-1.1.2.tgz#65bc2bc1db6d00e153b95ac7006f4573e289e9be" - integrity sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA== - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@dotenvx/dotenvx@^1.48.4": - version "1.48.4" - resolved "https://registry.yarnpkg.com/@dotenvx/dotenvx/-/dotenvx-1.48.4.tgz#697ca86d3cda1e6a7bde73bf129b9905149916d8" - integrity sha512-GpJWpGVI5JGhNzFlWOjCD3KMiN3xU1US4oLKQ7SiiGru4LvR7sUf3pDMpfjtlgzHStL5ydq4ekfZcRxWpHaJkA== - dependencies: - commander "^11.1.0" - dotenv "^17.2.1" - eciesjs "^0.4.10" - execa "^5.1.1" - fdir "^6.2.0" - ignore "^5.3.0" - object-treeify "1.1.33" - picomatch "^4.0.2" - which "^4.0.0" - -"@ecies/ciphers@^0.2.3": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@ecies/ciphers/-/ciphers-0.2.4.tgz#20a4e51f61d521e5e311eb49385d93d91087de51" - integrity sha512-t+iX+Wf5nRKyNzk8dviW3Ikb/280+aEJAnw9YXvCp2tYGPSkMki+NRY+8aNLmVFv3eNtMdvViPNOPxS8SZNP+w== - -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/config-array@^0.21.0": - version "0.21.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" - integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== - dependencies: - "@eslint/object-schema" "^2.1.6" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/config-helpers@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617" - integrity sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== - -"@eslint/core@^0.15.2": - version "0.15.2" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" - integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" - integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@9.33.0": - version "9.33.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.33.0.tgz#475c92fdddab59b8b8cab960e3de2564a44bf368" - integrity sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A== - -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== - -"@eslint/plugin-kit@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" - integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== - dependencies: - "@eslint/core" "^0.15.2" - levn "^0.4.1" - -"@humanfs/core@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" - integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== - -"@humanfs/node@^0.16.6": - version "0.16.6" - resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" - integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== +"@bundled-es-modules/cookie@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz#b41376af6a06b3e32a15241d927b840a9b4de507" + integrity sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw== dependencies: - "@humanfs/core" "^0.19.1" - "@humanwhocodes/retry" "^0.3.0" + cookie "^0.7.2" -"@humanwhocodes/module-importer@^1.0.1": +"@bundled-es-modules/statuses@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/retry@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" - integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== - -"@humanwhocodes/retry@^0.4.2": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" - integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + resolved "https://registry.yarnpkg.com/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz#761d10f44e51a94902c4da48675b71a76cc98872" + integrity sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg== + dependencies: + statuses "^2.0.1" + +"@bundled-es-modules/tough-cookie@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz#fa9cd3cedfeecd6783e8b0d378b4a99e52bde5d3" + integrity sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw== + dependencies: + "@types/tough-cookie" "^4.0.5" + tough-cookie "^4.1.4" + +"@inquirer/confirm@^5.0.0": + version "5.1.16" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.16.tgz#4f99603e5c8a1b471b819343f708c75e8abd2b88" + integrity sha512-j1a5VstaK5KQy8Mu8cHmuQvN1Zc62TbLhjJxwHvKPPKEoowSF6h/0UdOpA9DNdWZ+9Inq73+puRq1df6OJ8Sag== + dependencies: + "@inquirer/core" "^10.2.0" + "@inquirer/type" "^3.0.8" + +"@inquirer/core@^10.2.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.2.0.tgz#19ff527dbe0956891d825e320ecbc890bd6a1550" + integrity sha512-NyDSjPqhSvpZEMZrLCYUquWNl+XC/moEcVFqS55IEYIYsY0a1cUCevSqk7ctOlnm/RaSBU5psFryNlxcmGrjaA== + dependencies: + "@inquirer/figures" "^1.0.13" + "@inquirer/type" "^3.0.8" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^2.0.0" + signal-exit "^4.1.0" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.13": + version "1.0.13" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.13.tgz#ad0afd62baab1c23175115a9b62f511b6a751e45" + integrity sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw== + +"@inquirer/type@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.8.tgz#efc293ba0ed91e90e6267f1aacc1c70d20b8b4e8" + integrity sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -503,13 +394,6 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/create-cache-key-function@^30.0.0": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-30.0.5.tgz#6004225f7c143603bdb1a56099e9919cc056e581" - integrity sha512-W1kmkwPq/WTMQWgvbzWSCbXSqvjI6rkqBQCxuvYmd+g6o4b5gHP98ikfh/Ei0SKzHvWdI84TOXp0hRcbpr8Q0w== - dependencies: - "@jest/types" "30.0.5" - "@jest/environment@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" @@ -557,14 +441,6 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" -"@jest/pattern@30.0.1": - version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" - integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== - dependencies: - "@types/node" "*" - jest-regex-util "30.0.1" - "@jest/reporters@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" @@ -595,13 +471,6 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" - integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== - dependencies: - "@sinclair/typebox" "^0.34.0" - "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -659,19 +528,6 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" - integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== - dependencies: - "@jest/pattern" "30.0.1" - "@jest/schemas" "30.0.5" - "@types/istanbul-lib-coverage" "^2.0.6" - "@types/istanbul-reports" "^3.0.4" - "@types/node" "*" - "@types/yargs" "^17.0.33" - chalk "^4.1.2" - "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" @@ -692,25 +548,25 @@ "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": +"@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": +"@jridgewell/source-map@^0.3.3": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" + integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.5" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": version "0.3.30" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== @@ -718,71 +574,41 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@loaderkit/resolve@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@loaderkit/resolve/-/resolve-1.0.4.tgz#5ba1c2f4cc879d3fb2f066b71b8dc41a88bfb8e9" - integrity sha512-rJzYKVcV4dxJv+vW6jlvagF8zvGxHJ2+HTr1e2qOejfmGhAApgJHl8Aog4mMszxceTRiKTTbnpgmTO1bEZHV/A== - dependencies: - "@braidai/lang" "^1.0.0" - -"@noble/ciphers@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" - integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== - -"@noble/curves@^1.9.1": - version "1.9.7" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.7.tgz#79d04b4758a43e4bca2cbdc62e7771352fa6b951" - integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== +"@mswjs/interceptors@^0.39.1": + version "0.39.6" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.39.6.tgz#44094a578f20da4749d1a0eaf3cdb7973604004b" + integrity sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw== dependencies: - "@noble/hashes" "1.8.0" + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/logger" "^0.3.0" + "@open-draft/until" "^2.0.0" + is-node-process "^1.2.0" + outvariant "^1.4.3" + strict-event-emitter "^0.5.1" -"@noble/hashes@1.8.0", "@noble/hashes@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" - integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== +"@open-draft/deferred-promise@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" + integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== +"@open-draft/logger@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" + integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" + is-node-process "^1.2.0" + outvariant "^1.4.0" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgr/core@^0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" - integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== +"@open-draft/until@^2.0.0", "@open-draft/until@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" + integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sinclair/typebox@^0.34.0": - version "0.34.40" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.40.tgz#740056ea8d8aaada2ac1ce414c2f074798283b92" - integrity sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw== - -"@sindresorhus/is@^4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== - "@sinonjs/commons@^3.0.0": version "3.0.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" @@ -797,115 +623,10 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@swc/core-darwin-arm64@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.3.tgz#aaab6af81f255bdc9d3bf1d8d38457236cab1a02" - integrity sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw== - -"@swc/core-darwin-x64@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.13.3.tgz#2f65063a9ffb169eec810d2d063d93d21b8ec593" - integrity sha512-p0X6yhxmNUOMZrbeZ3ZNsPige8lSlSe1llllXvpCLkKKxN/k5vZt1sULoq6Nj4eQ7KeHQVm81/+AwKZyf/e0TA== - -"@swc/core-linux-arm-gnueabihf@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.3.tgz#1e4823f031f8ed8d77b0ea8ed70130cda2da6f1e" - integrity sha512-OmDoiexL2fVWvQTCtoh0xHMyEkZweQAlh4dRyvl8ugqIPEVARSYtaj55TBMUJIP44mSUOJ5tytjzhn2KFxFcBA== - -"@swc/core-linux-arm64-gnu@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.3.tgz#1a82f884e9a73c5fb80a94ec67ee98e255f93cdd" - integrity sha512-STfKku3QfnuUj6k3g9ld4vwhtgCGYIFQmsGPPgT9MK/dI3Lwnpe5Gs5t1inoUIoGNP8sIOLlBB4HV4MmBjQuhw== - -"@swc/core-linux-arm64-musl@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.3.tgz#f556489bec2451b8a3f28239e115a9480421c008" - integrity sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og== - -"@swc/core-linux-x64-gnu@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.3.tgz#29e78da291a6ac800e807771a40f6a41d18f0ead" - integrity sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA== - -"@swc/core-linux-x64-musl@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.3.tgz#5f2b0639f54f89468ad2e464ba6b45ce19adeca2" - integrity sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA== - -"@swc/core-win32-arm64-msvc@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.3.tgz#911185c11158b29a8884aea7036115a814a3725a" - integrity sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw== - -"@swc/core-win32-ia32-msvc@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.3.tgz#279044bfdba0853f1afd138f582952461544e8e8" - integrity sha512-nvehQVEOdI1BleJpuUgPLrclJ0TzbEMc+MarXDmmiRFwEUGqj+pnfkTSb7RZyS1puU74IXdK/YhTirHurtbI9w== - -"@swc/core-win32-x64-msvc@1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.3.tgz#6069e132be45ac34ecb4d72730db53c60d6a5475" - integrity sha512-A+JSKGkRbPLVV2Kwx8TaDAV0yXIXm/gc8m98hSkVDGlPBBmydgzNdWy3X7HTUBM7IDk7YlWE7w2+RUGjdgpTmg== - -"@swc/core@^1.3.102": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.13.3.tgz#7a8668d96a28b3431acc3b9652f2d3ff2b6e5531" - integrity sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w== - dependencies: - "@swc/counter" "^0.1.3" - "@swc/types" "^0.1.23" - optionalDependencies: - "@swc/core-darwin-arm64" "1.13.3" - "@swc/core-darwin-x64" "1.13.3" - "@swc/core-linux-arm-gnueabihf" "1.13.3" - "@swc/core-linux-arm64-gnu" "1.13.3" - "@swc/core-linux-arm64-musl" "1.13.3" - "@swc/core-linux-x64-gnu" "1.13.3" - "@swc/core-linux-x64-musl" "1.13.3" - "@swc/core-win32-arm64-msvc" "1.13.3" - "@swc/core-win32-ia32-msvc" "1.13.3" - "@swc/core-win32-x64-msvc" "1.13.3" - -"@swc/counter@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" - integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== - -"@swc/jest@^0.2.29": - version "0.2.39" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.39.tgz#482bee0adb0726fab1487a4f902a278ec563a6b7" - integrity sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA== - dependencies: - "@jest/create-cache-key-function" "^30.0.0" - "@swc/counter" "^0.1.3" - jsonc-parser "^3.2.0" - -"@swc/types@^0.1.23": - version "0.1.24" - resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.24.tgz#00f4343e2c966eac178cde89e8d821a784f7586d" - integrity sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng== - dependencies: - "@swc/counter" "^0.1.3" - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/babel__core@^7.1.14": version "7.20.5" @@ -940,7 +661,28 @@ dependencies: "@babel/types" "^7.28.2" -"@types/estree@^1.0.6": +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.8": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== @@ -952,7 +694,7 @@ dependencies: "@types/node" "*" -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== @@ -964,14 +706,14 @@ dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^3.0.0", "@types/istanbul-reports@^3.0.4": +"@types/istanbul-reports@^3.0.0": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.4.0": +"@types/jest@^29.5.14": version "29.5.14" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== @@ -979,182 +721,265 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.15": +"@types/jsdom@^20.0.0": + version "20.0.1" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" + integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/node@*", "@types/node@^24.3.0": +"@types/node@*": version "24.3.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec" integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== dependencies: undici-types "~7.10.0" -"@types/react@^19.1.10": - version "19.1.10" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.10.tgz#a05015952ef328e1b85579c839a71304b07d21d9" - integrity sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg== +"@types/node@^18.19.70": + version "18.19.123" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.123.tgz#08a3e4f5e0c73b8840c677b7635ce59d5dc1f76d" + integrity sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg== dependencies: - csstype "^3.0.2" + undici-types "~5.26.4" "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/statuses@^2.0.4": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.6.tgz#66748315cc9a96d63403baa8671b2c124f8633aa" + integrity sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA== + +"@types/tough-cookie@*", "@types/tough-cookie@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== -"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": +"@types/yargs@^17.0.8": version "17.0.33" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" - integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/type-utils" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - graphemer "^1.4.0" - ignore "^5.3.1" - natural-compare "^1.4.0" - ts-api-utils "^2.0.1" - -"@typescript-eslint/parser@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" - integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== - dependencies: - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/typescript-estree" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" - integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== - dependencies: - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - -"@typescript-eslint/type-utils@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" - integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== - dependencies: - "@typescript-eslint/typescript-estree" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - debug "^4.3.4" - ts-api-utils "^2.0.1" - -"@typescript-eslint/types@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" - integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== - -"@typescript-eslint/typescript-estree@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" - integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== - dependencies: - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.0.1" - -"@typescript-eslint/utils@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" - integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/typescript-estree" "8.31.1" - -"@typescript-eslint/visitor-keys@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" - integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== - dependencies: - "@typescript-eslint/types" "8.31.1" - eslint-visitor-keys "^4.2.0" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== + +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" + +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + +acorn-import-phases@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" + integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== + +acorn-walk@^8.0.2: version "8.3.4" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.15.0, acorn@^8.4.1: +acorn@^8.1.0, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.8.1: version "8.15.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" + ajv "^8.0.0" -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + fast-deep-equal "^3.1.3" -ansi-escapes@^4.2.1: +ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" -ansi-escapes@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" - integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== - dependencies: - environment "^1.0.0" - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.0.tgz#2f302e7550431b1b7762705fffb52cf1ffa20447" - integrity sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg== - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -1167,11 +992,6 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - anymatch@^3.0.3: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1180,11 +1000,6 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1192,10 +1007,10 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== babel-jest@^29.7.0: version "29.7.0" @@ -1273,13 +1088,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== - dependencies: - balanced-match "^1.0.0" - braces@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -1288,12 +1096,12 @@ braces@^3.0.3: fill-range "^7.1.1" browserslist@^4.24.0: - version "4.25.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.2.tgz#90c1507143742d743544ae6e92bca3348adff667" - integrity sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA== + version "4.25.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.4.tgz#ebdd0e1d1cf3911834bab3a6cd7b917d9babf5af" + integrity sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg== dependencies: - caniuse-lite "^1.0.30001733" - electron-to-chromium "^1.5.199" + caniuse-lite "^1.0.30001737" + electron-to-chromium "^1.5.211" node-releases "^2.0.19" update-browserslist-db "^1.1.3" @@ -1316,6 +1124,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1331,12 +1147,12 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001733: - version "1.0.30001735" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz#ba658fd3fd24a4106fd68d5ce472a2c251494dbe" - integrity sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w== +caniuse-lite@^1.0.30001737: + version "1.0.30001737" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz#8292bb7591932ff09e9a765f12fdf5629a241ccc" + integrity sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw== -chalk@^4.0.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1344,60 +1160,30 @@ chalk@^4.0.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.4.1: - version "5.6.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.0.tgz#a1a8d294ea3526dbb77660f12649a08490e33ab8" - integrity sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ== - char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.3: +cjs-module-lexer@^1.0.0: version "1.4.3" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-highlight@^2.1.11: - version "2.1.11" - resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" - integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== - dependencies: - chalk "^4.0.0" - highlight.js "^10.7.1" - mz "^2.4.0" - parse5 "^5.1.1" - parse5-htmlparser2-tree-adapter "^6.0.0" - yargs "^16.0.0" - -cli-table3@^0.6.3, cli-table3@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" - integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== cliui@^8.0.1: version "8.0.1" @@ -1430,20 +1216,17 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" - integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" -commander@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.0.tgz#f244fc74a92343514e56229f16ef5c5e22ced5e9" - integrity sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA== +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== concat-map@0.0.1: version "0.0.1" @@ -1455,6 +1238,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -1468,12 +1256,7 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.3, cross-spawn@^7.0.6: +cross-spawn@^7.0.3: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1482,33 +1265,59 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -csstype@^3.0.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7: +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.4.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== dependencies: ms "^2.1.3" +decimal.js@^10.4.2: + version "10.6.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== + dedent@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1519,30 +1328,26 @@ diff-sequences@^29.6.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dotenv@^17.2.1: - version "17.2.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.1.tgz#6f32e10faf014883515538dc922a0fb8765d9b32" - integrity sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" -eciesjs@^0.4.10: - version "0.4.15" - resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.4.15.tgz#8c7191ce425c54627ee5c65328ab54eaa6ed4556" - integrity sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: - "@ecies/ciphers" "^0.2.3" - "@noble/ciphers" "^1.3.0" - "@noble/curves" "^1.9.1" - "@noble/hashes" "^1.8.0" + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" -electron-to-chromium@^1.5.199: - version "1.5.203" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz#ef7fc2f7e1b816fa4535c861d1ec1348204142b6" - integrity sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g== +electron-to-chromium@^1.5.211: + version "1.5.211" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz#749317bf9cf894c06f67980940cf8074e5eb08ca" + integrity sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw== emittery@^0.13.1: version "0.13.1" @@ -1554,15 +1359,18 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojilib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" - integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.3: + version "5.18.3" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44" + integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" -environment@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" - integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== error-ex@^1.3.1: version "1.3.2" @@ -1571,6 +1379,38 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -1581,104 +1421,30 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-prettier@^5.4.1: - version "5.5.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz#9d61c4ea11de5af704d4edf108c82ccfa7f2e61c" - integrity sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg== - dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.11.7" - -eslint-plugin-unused-imports@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" - integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== - -eslint-scope@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" - integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== +escodegen@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== dependencies: - esrecurse "^4.3.0" + esprima "^4.0.1" estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" - integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== - -eslint@^9.20.1: - version "9.33.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.33.0.tgz#cc186b3d9eb0e914539953d6a178a5b413997b73" - integrity sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.21.0" - "@eslint/config-helpers" "^0.3.1" - "@eslint/core" "^0.15.2" - "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.33.0" - "@eslint/plugin-kit" "^0.3.5" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.2" - "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.4.0" - eslint-visitor-keys "^4.2.1" - espree "^10.4.0" - esquery "^1.5.0" esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" + optionalDependencies: + source-map "~0.6.1" -espree@^10.0.1, espree@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" - integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: - acorn "^8.15.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.1" + esrecurse "^4.3.0" + estraverse "^4.1.1" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -1686,7 +1452,12 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^5.1.0, estraverse@^5.2.0: +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -1696,7 +1467,12 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -execa@^5.0.0, execa@^5.1.1: +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -1727,43 +1503,20 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-diff@^1.1.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - -fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== - dependencies: - reusify "^1.0.4" +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== fb-watchman@^2.0.0: version "2.0.2" @@ -1772,23 +1525,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fdir@^6.2.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" - integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== - -fflate@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" - integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== - -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - dependencies: - flat-cache "^4.0.0" - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -1804,26 +1540,16 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== +form-data@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== dependencies: - flatted "^3.2.9" - keyv "^4.5.4" - -flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" fs.realpath@^1.0.0: version "1.0.0" @@ -1850,34 +1576,44 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stdin@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" - integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== glob@^7.1.3, glob@^7.1.4: version "7.2.3" @@ -1891,31 +1627,20 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -globals@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" - integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphql@^16.8.1: + version "16.11.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.11.0.tgz#96d17f66370678027fdf59b2d4c20b4efaa8a633" + integrity sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw== handlebars@^4.7.8: version "4.7.8" @@ -1934,6 +1659,18 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -1941,48 +1678,52 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -highlight.js@^10.7.1: - version "10.7.3" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" - integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== +headers-polyfill@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" + integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== + +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@^0.6.3: +iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ignore-walk@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" - integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== - dependencies: - minimatch "^5.0.1" - -ignore@^5.2.0, ignore@^5.3.0, ignore@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -import-fresh@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - import-local@^3.0.2: version "3.2.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" @@ -1996,11 +1737,6 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2009,7 +1745,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2026,11 +1762,6 @@ is-core-module@^2.16.0: dependencies: hasown "^2.0.2" -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -2041,18 +1772,21 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" +is-node-process@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" + integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -2063,11 +1797,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isexe@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" - integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -2114,9 +1843,9 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -2229,6 +1958,20 @@ jest-each@^29.7.0: jest-util "^29.7.0" pretty-format "^29.7.0" +jest-environment-jsdom@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/jsdom" "^20.0.0" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jsdom "^20.0.0" + jest-environment-node@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" @@ -2312,11 +2055,6 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@30.0.1: - version "30.0.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" - integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== - jest-regex-util@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" @@ -2464,6 +2202,15 @@ jest-watcher@^29.7.0: jest-util "^29.7.0" string-length "^4.0.1" +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + jest-worker@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" @@ -2474,7 +2221,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.4.0: +jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -2497,55 +2244,58 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== + dependencies: + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" jsesc@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json5@^2.2.2, json5@^2.2.3: +json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonc-parser@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" - integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== - -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -2556,19 +2306,16 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -2576,28 +2323,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lru-cache@^10.4.3: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2612,7 +2342,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@^1.1.1, make-error@^1.3.6: +make-error@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -2624,35 +2354,17 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -marked-terminal@^7.1.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-7.3.0.tgz#7a86236565f3dd530f465ffce9c3f8b62ef270e8" - integrity sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw== - dependencies: - ansi-escapes "^7.0.0" - ansi-regex "^6.1.0" - chalk "^5.4.1" - cli-highlight "^2.1.11" - cli-table3 "^0.6.5" - node-emoji "^2.2.0" - supports-hyperlinks "^3.1.0" - -marked@^9.1.2: - version "9.1.6" - resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.6.tgz#5d2a3f8180abfbc5d62e3258a38a1c19c0381695" - integrity sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4, micromatch@^4.0.8: +micromatch@^4.0.0, micromatch@^4.0.4: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -2660,55 +2372,68 @@ micromatch@^4.0.4, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.5: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mri@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" - integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== - ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mz@^2.4.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" +msw@^2.8.4: + version "2.10.5" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.10.5.tgz#3e43f12e97581c260bf38d8817732b9fec3bfdb0" + integrity sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A== + dependencies: + "@bundled-es-modules/cookie" "^2.0.1" + "@bundled-es-modules/statuses" "^1.0.1" + "@bundled-es-modules/tough-cookie" "^0.1.6" + "@inquirer/confirm" "^5.0.0" + "@mswjs/interceptors" "^0.39.1" + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/until" "^2.1.0" + "@types/cookie" "^0.6.0" + "@types/statuses" "^2.0.4" + graphql "^16.8.1" + headers-polyfill "^4.0.2" + is-node-process "^1.2.0" + outvariant "^1.4.3" + path-to-regexp "^6.3.0" + picocolors "^1.1.1" + strict-event-emitter "^0.5.1" + type-fest "^4.26.1" + yargs "^17.7.2" + +mute-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" + integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== natural-compare@^1.4.0: version "1.4.0" @@ -2720,16 +2445,6 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-emoji@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.2.0.tgz#1d000e3c76e462577895be1b436f4aa2d6760eb0" - integrity sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw== - dependencies: - "@sindresorhus/is" "^4.6.0" - char-regex "^1.0.2" - emojilib "^2.4.0" - skin-tone "^2.0.0" - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -2745,28 +2460,6 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-bundled@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" - integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== - dependencies: - npm-normalize-package-bin "^2.0.0" - -npm-normalize-package-bin@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" - integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== - -npm-packlist@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" - integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== - dependencies: - glob "^8.0.1" - ignore-walk "^5.0.1" - npm-bundled "^2.0.0" - npm-normalize-package-bin "^2.0.0" - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -2774,15 +2467,10 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-treeify@1.1.33: - version "1.1.33" - resolved "https://registry.yarnpkg.com/object-treeify/-/object-treeify-1.1.33.tgz#f06fece986830a3cba78ddd32d4c11d1f76cdf40" - integrity sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A== +nwsapi@^2.2.2: + version "2.2.21" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.21.tgz#8df7797079350adda208910d8c33fc4c2d7520c3" + integrity sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA== once@^1.3.0: version "1.4.0" @@ -2798,24 +2486,10 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -p-all@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" - integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== - dependencies: - p-map "^4.0.0" +outvariant@^1.4.0, outvariant@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.3.tgz#221c1bfc093e8fec7075497e7799fdbf43d14873" + integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA== p-limit@^2.2.0: version "2.3.0" @@ -2824,7 +2498,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -2838,32 +2512,11 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -2874,22 +2527,12 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5-htmlparser2-tree-adapter@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== +parse5@^7.0.0, parse5@^7.1.1: + version "7.3.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== dependencies: - parse5 "^6.0.1" - -parse5@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + entities "^6.0.0" path-exists@^4.0.0: version "4.0.0" @@ -2911,6 +2554,11 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-to-regexp@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" + integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== + picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -2921,11 +2569,6 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== - pirates@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" @@ -2938,19 +2581,7 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^3.0.0: +prettier@^3.4.2: version "3.6.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== @@ -2972,16 +2603,14 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -publint@^0.2.12: - version "0.2.12" - resolved "https://registry.yarnpkg.com/publint/-/publint-0.2.12.tgz#d25cd6bd243d5bdd640344ecdddb3eeafdcc4059" - integrity sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw== +psl@^1.1.33: + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== dependencies: - npm-packlist "^5.1.3" - picocolors "^1.1.1" - sade "^1.8.1" + punycode "^2.3.1" -punycode@^2.1.0: +punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -2991,10 +2620,17 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" react-dom@^19.1.1: version "19.1.1" @@ -3013,20 +2649,21 @@ react@^19.1.1: resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af" integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ== -readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -3034,11 +2671,6 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -3058,26 +2690,7 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -sade@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" - integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== - dependencies: - mri "^1.1.0" - -safe-buffer@~5.2.0: +safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -3087,21 +2700,45 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.26.0: version "0.26.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337" integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== +schema-utils@^4.3.0, schema-utils@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.2.tgz#0c10878bf4a73fd2b1dfd14b9462b26788c806ae" + integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.2: +semver@^7.3.4, semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: version "7.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -3119,18 +2756,16 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -skin-tone@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" - integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== - dependencies: - unicode-emoji-modifier-base "^1.0.0" - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3144,11 +2779,24 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@^0.6.1: +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.4: + version "0.7.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -3161,6 +2809,16 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +statuses@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +strict-event-emitter@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" + integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -3169,13 +2827,6 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-to-stream@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" - integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== - dependencies: - readable-stream "^3.4.0" - string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -3185,13 +2836,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -3199,11 +2843,6 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -3219,12 +2858,7 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -superstruct@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" - integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== - -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -3238,25 +2872,41 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz#b8e485b179681dea496a1e7abdf8985bd3145461" - integrity sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -synckit@^0.11.7: - version "0.11.11" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" - integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== - dependencies: - "@pkgr/core" "^0.2.9" +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.3.tgz#4b67b635b2d97578a06a2713d2f04800c237e99b" + integrity sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg== + +terser-webpack-plugin@^5.3.11: + version "5.3.14" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" + integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser@^5.31.1: + version "5.43.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.43.1.tgz#88387f4f9794ff1a29e7ad61fb2932e25b4fdb6d" + integrity sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.14.0" + commander "^2.20.0" + source-map-support "~0.5.20" test-exclude@^6.0.0: version "6.0.0" @@ -3267,20 +2917,6 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3293,12 +2929,24 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -ts-api-utils@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" - integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== +tough-cookie@^4.1.2, tough-cookie@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" -ts-jest@^29.1.0: +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +ts-jest@^29.3.4: version "29.4.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.1.tgz#42d33beb74657751d315efb9a871fe99e3b9b519" integrity sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw== @@ -3313,60 +2961,16 @@ ts-jest@^29.1.0: type-fest "^4.41.0" yargs-parser "^21.1.1" -ts-node@^10.5.0: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": - version "1.1.9" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" - dependencies: - debug "^4.3.7" - fast-glob "^3.3.2" - get-stdin "^8.0.0" - p-all "^3.0.0" - picocolors "^1.1.1" - signal-exit "^3.0.7" - string-to-stream "^3.0.1" - superstruct "^1.0.4" - tslib "^2.8.1" - yargs "^17.7.2" - -tsconfig-paths@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== +ts-loader@^9.5.1: + version "9.5.4" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.4.tgz#44b571165c10fb5a90744aa5b7e119233c4f4585" + integrity sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ== dependencies: - prelude-ls "^1.2.1" + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + source-map "^0.7.4" type-detect@4.0.8: version "4.0.8" @@ -3378,44 +2982,35 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^4.41.0: +type-fest@^4.26.1, type-fest@^4.41.0: version "4.41.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== -typescript-eslint@8.31.1: - version "8.31.1" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.31.1.tgz#b77ab1e48ced2daab9225ff94bab54391a4af69b" - integrity sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA== - dependencies: - "@typescript-eslint/eslint-plugin" "8.31.1" - "@typescript-eslint/parser" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - -typescript@5.6.1-rc: - version "5.6.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" - integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== - -typescript@5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== +typescript@~5.7.2: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== uglify-js@^3.1.4: version "3.19.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici-types@~7.10.0: version "7.10.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350" integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== -unicode-emoji-modifier-base@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" - integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== update-browserslist-db@^1.1.3: version "1.1.3" @@ -3425,22 +3020,13 @@ update-browserslist-db@^1.1.3: escalade "^3.2.0" picocolors "^1.1.1" -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + querystringify "^2.1.1" + requires-port "^1.0.0" v8-to-istanbul@^9.0.1: version "9.3.0" @@ -3451,10 +3037,12 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" -validate-npm-package-name@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" - integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== + dependencies: + xml-name-validator "^4.0.0" walker@^1.0.8: version "1.0.8" @@ -3463,6 +3051,75 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" +watchpack@^2.4.1: + version "2.4.4" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" + integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +webpack-sources@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" + integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== + +webpack@^5.97.1: + version "5.101.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.101.3.tgz#3633b2375bb29ea4b06ffb1902734d977bc44346" + integrity sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.8" + "@types/json-schema" "^7.0.15" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.15.0" + acorn-import-phases "^1.0.3" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.3" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^4.3.2" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.11" + watchpack "^2.4.1" + webpack-sources "^3.3.3" + +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -3470,23 +3127,20 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -which@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" - integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== - dependencies: - isexe "^3.1.1" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3509,6 +3163,21 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^8.11.0: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -3519,29 +3188,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^16.0.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" @@ -3555,17 +3206,17 @@ yargs@^17.3.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod@^4.0.17: - version "4.0.17" - resolved "https://registry.yarnpkg.com/zod/-/zod-4.0.17.tgz#95931170715f73f7426c385c237b7477750d6c8d" - integrity sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ== +yoctocolors-cjs@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + +zod@^4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.5.tgz#7a21fc3178928ede50a28f7d0db4414c4cdb0161" + integrity sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==