From 7e2ea72ab8112a91350bb36461092fb7a2860f95 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 21:18:06 +0000 Subject: [PATCH] SDK regeneration --- .devcontainer/devcontainer.json | 15 - .fernignore | 1 + .github/workflows/ci.yml | 96 +- .github/workflows/publish-npm.yml | 32 - .github/workflows/release-doctor.yml | 21 - .gitignore | 12 +- .npmignore | 10 + .nvmrc | 1 - .prettierignore | 7 - .prettierrc.json | 7 - .prettierrc.yml | 2 + .release-please-manifest.json | 3 - .stats.yml | 4 - Brewfile | 1 - CHANGELOG.md | 157 -- CONTRIBUTING.md | 107 - LICENSE | 201 -- README.md | 353 ++- SECURITY.md | 27 - api.md | 104 - assets/cloud-banner-js.png | Bin 32184 -> 0 bytes bin/check-release-environment | 22 - bin/publish-npm | 61 - eslint.config.mjs | 42 - examples/.env.example | 22 - examples/.keep | 4 - examples/retrieve.ts | 106 - examples/run.ts | 63 - examples/stream.ts | 94 - examples/utils.ts | 34 - examples/webhook.ts | 177 -- jest.config.mjs | 42 + jest.config.ts | 23 - package.json | 159 +- reference.md | 1310 +++++++++ release-please-config.json | 64 - scripts/bootstrap | 18 - scripts/build | 51 - scripts/format | 12 - scripts/lint | 21 - scripts/mock | 41 - scripts/rename-to-esm-files.js | 123 + scripts/test | 56 - scripts/utils/attw-report.cjs | 24 - scripts/utils/check-is-in-git-install.sh | 9 - scripts/utils/check-version.cjs | 20 - scripts/utils/fix-index-exports.cjs | 17 - scripts/utils/git-swap.sh | 13 - scripts/utils/make-dist-package-json.cjs | 21 - scripts/utils/postprocess-files.cjs | 94 - scripts/utils/upload-artifact.sh | 25 - src/Client.ts | 80 + src/api-promise.ts | 2 - src/api/errors/BadRequestError.ts | 18 + src/api/errors/InternalServerError.ts | 18 + src/api/errors/NotFoundError.ts | 18 + src/api/errors/PaymentRequiredError.ts | 19 + src/api/errors/UnprocessableEntityError.ts | 18 + src/api/errors/index.ts | 5 + src/api/index.ts | 3 + src/api/resources/account/client/Client.ts | 114 + src/api/resources/account/client/index.ts | 1 + src/api/resources/account/index.ts | 1 + src/api/resources/files/client/Client.ts | 325 +++ src/api/resources/files/client/index.ts | 2 + .../client/requests/UploadFileRequest.ts | 53 + .../resources/files/client/requests/index.ts | 1 + src/api/resources/files/index.ts | 1 + src/api/resources/index.ts | 9 + src/api/resources/profiles/client/Client.ts | 372 +++ src/api/resources/profiles/client/index.ts | 2 + .../ListProfilesProfilesGetRequest.ts | 12 + .../profiles/client/requests/index.ts | 1 + src/api/resources/profiles/index.ts | 1 + src/api/resources/sessions/client/Client.ts | 622 +++++ src/api/resources/sessions/client/index.ts | 2 + .../ListSessionsSessionsGetRequest.ts | 15 + .../client/requests/UpdateSessionRequest.ts | 9 + .../sessions/client/requests/index.ts | 2 + src/api/resources/sessions/index.ts | 1 + src/api/resources/tasks/client/Client.ts | 498 ++++ src/api/resources/tasks/client/index.ts | 2 + .../client/requests/CreateTaskRequest.ts | 28 + .../requests/ListTasksTasksGetRequest.ts | 18 + .../client/requests/UpdateTaskRequest.ts | 15 + .../resources/tasks/client/requests/index.ts | 3 + src/api/resources/tasks/index.ts | 1 + src/api/types/AccountNotFoundError.ts | 10 + src/api/types/AccountView.ts | 21 + src/api/types/AgentSettings.ts | 25 + src/api/types/BadRequestErrorBody.ts | 7 + src/api/types/BrowserSettings.ts | 19 + src/api/types/CreditsDeductionError.ts | 10 + src/api/types/DownloadUrlGenerationError.ts | 10 + src/api/types/FileView.ts | 15 + src/api/types/HttpValidationError.ts | 9 + src/api/types/InsufficientCreditsError.ts | 10 + src/api/types/InternalServerErrorBody.ts | 10 + src/api/types/NotFoundErrorBody.ts | 7 + src/api/types/OutputFileNotFoundError.ts | 10 + .../types/PersistenceProfileNotFoundError.ts | 10 + src/api/types/ProfileListResponse.ts | 18 + src/api/types/ProfileView.ts | 23 + src/api/types/ProxyCountryCode.ts | 17 + src/api/types/ProxySettings.ts | 19 + src/api/types/SessionHasRunningTaskError.ts | 10 + src/api/types/SessionItemView.ts | 23 + src/api/types/SessionListResponse.ts | 18 + src/api/types/SessionNotFoundError.ts | 10 + src/api/types/SessionStatus.ts | 16 + src/api/types/SessionStoppedError.ts | 10 + src/api/types/SessionUpdateAction.ts | 11 + src/api/types/SessionView.ts | 29 + src/api/types/ShareNotFoundError.ts | 10 + src/api/types/ShareView.ts | 19 + src/api/types/SupportedLlMs.ts | 29 + src/api/types/TaskCreatedResponse.ts | 15 + src/api/types/TaskItemView.ts | 40 + src/api/types/TaskListResponse.ts | 18 + src/api/types/TaskLogFileResponse.ts | 13 + src/api/types/TaskNotFoundError.ts | 10 + src/api/types/TaskOutputFileResponse.ts | 17 + src/api/types/TaskSessionStatus.ts | 16 + src/api/types/TaskSessionView.ts | 23 + src/api/types/TaskStatus.ts | 20 + src/api/types/TaskStepView.ts | 25 + src/api/types/TaskUpdateAction.ts | 20 + src/api/types/TaskUploadedFileResponse.ts | 17 + src/api/types/TaskView.ts | 45 + src/api/types/UnsupportedContentTypeError.ts | 10 + .../types/UploadFilePresignedUrlResponse.ts | 21 + .../types/UserUploadedFileNotFoundError.ts | 10 + src/api/types/ValidationError.ts | 17 + src/api/types/index.ts | 46 + src/client.ts | 824 ------ src/core/README.md | 3 - src/core/api-promise.ts | 92 - src/core/error.ts | 130 - src/core/fetcher/APIResponse.ts | 23 + src/core/fetcher/BinaryResponse.ts | 36 + src/core/fetcher/Fetcher.ts | 163 ++ src/core/fetcher/Headers.ts | 93 + src/core/fetcher/HttpResponsePromise.ts | 116 + src/core/fetcher/RawResponse.ts | 61 + src/core/fetcher/ResponseWithBody.ts | 7 + src/core/fetcher/Supplier.ts | 11 + src/core/fetcher/createRequestUrl.ts | 6 + src/core/fetcher/getErrorResponseBody.ts | 32 + src/core/fetcher/getFetchFn.ts | 3 + src/core/fetcher/getHeader.ts | 8 + src/core/fetcher/getRequestBody.ts | 16 + src/core/fetcher/getResponseBody.ts | 43 + src/core/fetcher/index.ts | 9 + src/core/fetcher/makeRequest.ts | 44 + src/core/fetcher/requestWithRetries.ts | 33 + src/core/fetcher/signals.ts | 38 + src/core/headers.ts | 35 + src/core/index.ts | 3 + src/core/json.ts | 27 + src/core/resource.ts | 11 - src/core/runtime/index.ts | 1 + src/core/runtime/runtime.ts | 133 + src/core/uploads.ts | 2 - src/core/url/index.ts | 2 + src/core/url/join.ts | 80 + src/core/url/qs.ts | 74 + src/error.ts | 2 - src/errors/BrowserUseError.ts | 55 + src/errors/BrowserUseTimeoutError.ts | 10 + src/errors/index.ts | 2 + src/index.ts | 25 +- src/internal/README.md | 3 - src/internal/builtin-types.ts | 93 - src/internal/detect-platform.ts | 196 -- src/internal/errors.ts | 33 - src/internal/headers.ts | 97 - src/internal/parse.ts | 50 - src/internal/request-options.ts | 91 - src/internal/shim-types.ts | 26 - src/internal/shims.ts | 107 - src/internal/to-file.ts | 154 -- src/internal/types.ts | 95 - src/internal/uploads.ts | 187 -- src/internal/utils.ts | 8 - src/internal/utils/base64.ts | 40 - src/internal/utils/bytes.ts | 32 - src/internal/utils/env.ts | 18 - src/internal/utils/log.ts | 127 - src/internal/utils/path.ts | 88 - src/internal/utils/sleep.ts | 3 - src/internal/utils/uuid.ts | 17 - src/internal/utils/values.ts | 105 - src/lib/.keep | 4 - src/lib/bin/auth.ts | 63 - src/lib/bin/cli.ts | 11 - src/lib/bin/commands/listen.ts | 183 -- src/lib/nextjs/hooks/useBrowserUse.ts | 37 - src/lib/nextjs/server/utils.ts | 69 - src/lib/parse.ts | 46 - src/lib/stream.ts | 14 - src/lib/types.ts | 73 - src/lib/webhooks.ts | 119 - src/resource.ts | 2 - src/resources.ts | 1 - src/resources/agent-profiles.ts | 245 -- src/resources/browser-profiles.ts | 255 -- src/resources/index.ts | 46 - src/resources/sessions.ts | 3 - src/resources/sessions/index.ts | 11 - src/resources/sessions/public-share.ts | 109 - src/resources/sessions/sessions.ts | 240 -- src/resources/tasks.ts | 783 ------ src/resources/users.ts | 3 - src/resources/users/index.ts | 4 - src/resources/users/me.ts | 3 - src/resources/users/me/files.ts | 95 - src/resources/users/me/index.ts | 4 - src/resources/users/me/me.ts | 68 - src/resources/users/users.ts | 15 - src/uploads.ts | 2 - src/version.ts | 2 +- tests/BrowserTestEnvironment.ts | 17 + tests/api-resources/agent-profiles.test.ts | 93 - tests/api-resources/browser-profiles.test.ts | 94 - .../sessions/public-share.test.ts | 46 - tests/api-resources/sessions/sessions.test.ts | 76 - tests/api-resources/tasks.test.ts | 155 -- tests/api-resources/users/me/files.test.ts | 35 - tests/api-resources/users/me/me.test.ts | 22 - tests/base64.test.ts | 80 - tests/buildHeaders.test.ts | 88 - tests/custom.test.ts | 13 + tests/form.test.ts | 85 - tests/index.test.ts | 734 ------ tests/lib/webhooks.test.ts | 114 - tests/mock-server/MockServer.ts | 29 + tests/mock-server/MockServerPool.ts | 106 + tests/mock-server/mockEndpointBuilder.ts | 210 ++ tests/mock-server/randomBaseUrl.ts | 4 + tests/mock-server/setup.ts | 10 + tests/mock-server/withHeaders.ts | 70 + tests/mock-server/withJson.ts | 158 ++ tests/path.test.ts | 462 ---- tests/stringifyQuery.test.ts | 29 - tests/tsconfig.json | 10 + tests/unit/fetcher/Fetcher.test.ts | 256 ++ .../unit/fetcher/HttpResponsePromise.test.ts | 143 + tests/unit/fetcher/RawResponse.test.ts | 34 + tests/unit/fetcher/createRequestUrl.test.ts | 160 ++ tests/unit/fetcher/getRequestBody.test.ts | 65 + tests/unit/fetcher/getResponseBody.test.ts | 77 + tests/unit/fetcher/makeRequest.test.ts | 53 + tests/unit/fetcher/requestWithRetries.test.ts | 132 + tests/unit/fetcher/signals.test.ts | 69 + tests/unit/fetcher/test-file.txt | 1 + tests/unit/url/join.test.ts | 120 + tests/unit/url/qs.test.ts | 187 ++ tests/uploads.test.ts | 107 - tests/wire/.gitkeep | 0 tests/wire/account.test.ts | 31 + tests/wire/files.test.ts | 86 + tests/wire/profiles.test.ts | 101 + tests/wire/sessions.test.ts | 254 ++ tests/wire/tasks.test.ts | 290 ++ tsc-multi.json | 15 - tsconfig.base.json | 16 + tsconfig.build.json | 18 - tsconfig.cjs.json | 9 + tsconfig.deno.json | 15 - tsconfig.dist-src.json | 11 - tsconfig.esm.json | 9 + tsconfig.json | 37 +- yarn.lock | 2341 +++++++---------- 273 files changed, 9840 insertions(+), 10909 deletions(-) delete mode 100644 .devcontainer/devcontainer.json create mode 100644 .fernignore delete mode 100644 .github/workflows/publish-npm.yml delete mode 100644 .github/workflows/release-doctor.yml create mode 100644 .npmignore delete mode 100644 .nvmrc delete mode 100644 .prettierignore delete mode 100644 .prettierrc.json create mode 100644 .prettierrc.yml delete mode 100644 .release-please-manifest.json delete mode 100644 .stats.yml delete mode 100644 Brewfile delete mode 100644 CHANGELOG.md delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE delete mode 100644 SECURITY.md delete mode 100644 api.md delete mode 100644 assets/cloud-banner-js.png delete mode 100644 bin/check-release-environment delete mode 100644 bin/publish-npm delete mode 100644 eslint.config.mjs delete mode 100644 examples/.env.example delete mode 100644 examples/.keep delete mode 100755 examples/retrieve.ts delete mode 100755 examples/run.ts delete mode 100755 examples/stream.ts delete mode 100644 examples/utils.ts delete mode 100755 examples/webhook.ts create mode 100644 jest.config.mjs delete mode 100644 jest.config.ts create mode 100644 reference.md delete mode 100644 release-please-config.json delete mode 100755 scripts/bootstrap delete mode 100755 scripts/build delete mode 100755 scripts/format delete mode 100755 scripts/lint delete mode 100755 scripts/mock create mode 100644 scripts/rename-to-esm-files.js delete mode 100755 scripts/test delete mode 100644 scripts/utils/attw-report.cjs delete mode 100755 scripts/utils/check-is-in-git-install.sh delete mode 100644 scripts/utils/check-version.cjs delete mode 100644 scripts/utils/fix-index-exports.cjs delete mode 100755 scripts/utils/git-swap.sh delete mode 100644 scripts/utils/make-dist-package-json.cjs delete mode 100644 scripts/utils/postprocess-files.cjs delete mode 100755 scripts/utils/upload-artifact.sh create mode 100644 src/Client.ts delete mode 100644 src/api-promise.ts create mode 100644 src/api/errors/BadRequestError.ts create mode 100644 src/api/errors/InternalServerError.ts create mode 100644 src/api/errors/NotFoundError.ts create mode 100644 src/api/errors/PaymentRequiredError.ts create mode 100644 src/api/errors/UnprocessableEntityError.ts create mode 100644 src/api/errors/index.ts create mode 100644 src/api/index.ts create mode 100644 src/api/resources/account/client/Client.ts create mode 100644 src/api/resources/account/client/index.ts create mode 100644 src/api/resources/account/index.ts create mode 100644 src/api/resources/files/client/Client.ts create mode 100644 src/api/resources/files/client/index.ts create mode 100644 src/api/resources/files/client/requests/UploadFileRequest.ts create mode 100644 src/api/resources/files/client/requests/index.ts create mode 100644 src/api/resources/files/index.ts create mode 100644 src/api/resources/index.ts create mode 100644 src/api/resources/profiles/client/Client.ts create mode 100644 src/api/resources/profiles/client/index.ts create mode 100644 src/api/resources/profiles/client/requests/ListProfilesProfilesGetRequest.ts create mode 100644 src/api/resources/profiles/client/requests/index.ts create mode 100644 src/api/resources/profiles/index.ts create mode 100644 src/api/resources/sessions/client/Client.ts create mode 100644 src/api/resources/sessions/client/index.ts create mode 100644 src/api/resources/sessions/client/requests/ListSessionsSessionsGetRequest.ts create mode 100644 src/api/resources/sessions/client/requests/UpdateSessionRequest.ts create mode 100644 src/api/resources/sessions/client/requests/index.ts create mode 100644 src/api/resources/sessions/index.ts create mode 100644 src/api/resources/tasks/client/Client.ts create mode 100644 src/api/resources/tasks/client/index.ts create mode 100644 src/api/resources/tasks/client/requests/CreateTaskRequest.ts create mode 100644 src/api/resources/tasks/client/requests/ListTasksTasksGetRequest.ts create mode 100644 src/api/resources/tasks/client/requests/UpdateTaskRequest.ts create mode 100644 src/api/resources/tasks/client/requests/index.ts create mode 100644 src/api/resources/tasks/index.ts create mode 100644 src/api/types/AccountNotFoundError.ts create mode 100644 src/api/types/AccountView.ts create mode 100644 src/api/types/AgentSettings.ts create mode 100644 src/api/types/BadRequestErrorBody.ts create mode 100644 src/api/types/BrowserSettings.ts create mode 100644 src/api/types/CreditsDeductionError.ts create mode 100644 src/api/types/DownloadUrlGenerationError.ts create mode 100644 src/api/types/FileView.ts create mode 100644 src/api/types/HttpValidationError.ts create mode 100644 src/api/types/InsufficientCreditsError.ts create mode 100644 src/api/types/InternalServerErrorBody.ts create mode 100644 src/api/types/NotFoundErrorBody.ts create mode 100644 src/api/types/OutputFileNotFoundError.ts create mode 100644 src/api/types/PersistenceProfileNotFoundError.ts create mode 100644 src/api/types/ProfileListResponse.ts create mode 100644 src/api/types/ProfileView.ts create mode 100644 src/api/types/ProxyCountryCode.ts create mode 100644 src/api/types/ProxySettings.ts create mode 100644 src/api/types/SessionHasRunningTaskError.ts create mode 100644 src/api/types/SessionItemView.ts create mode 100644 src/api/types/SessionListResponse.ts create mode 100644 src/api/types/SessionNotFoundError.ts create mode 100644 src/api/types/SessionStatus.ts create mode 100644 src/api/types/SessionStoppedError.ts create mode 100644 src/api/types/SessionUpdateAction.ts create mode 100644 src/api/types/SessionView.ts create mode 100644 src/api/types/ShareNotFoundError.ts create mode 100644 src/api/types/ShareView.ts create mode 100644 src/api/types/SupportedLlMs.ts create mode 100644 src/api/types/TaskCreatedResponse.ts create mode 100644 src/api/types/TaskItemView.ts create mode 100644 src/api/types/TaskListResponse.ts create mode 100644 src/api/types/TaskLogFileResponse.ts create mode 100644 src/api/types/TaskNotFoundError.ts create mode 100644 src/api/types/TaskOutputFileResponse.ts create mode 100644 src/api/types/TaskSessionStatus.ts create mode 100644 src/api/types/TaskSessionView.ts create mode 100644 src/api/types/TaskStatus.ts create mode 100644 src/api/types/TaskStepView.ts create mode 100644 src/api/types/TaskUpdateAction.ts create mode 100644 src/api/types/TaskUploadedFileResponse.ts create mode 100644 src/api/types/TaskView.ts create mode 100644 src/api/types/UnsupportedContentTypeError.ts create mode 100644 src/api/types/UploadFilePresignedUrlResponse.ts create mode 100644 src/api/types/UserUploadedFileNotFoundError.ts create mode 100644 src/api/types/ValidationError.ts create mode 100644 src/api/types/index.ts delete mode 100644 src/client.ts delete mode 100644 src/core/README.md delete mode 100644 src/core/api-promise.ts delete mode 100644 src/core/error.ts create mode 100644 src/core/fetcher/APIResponse.ts create mode 100644 src/core/fetcher/BinaryResponse.ts create mode 100644 src/core/fetcher/Fetcher.ts create mode 100644 src/core/fetcher/Headers.ts create mode 100644 src/core/fetcher/HttpResponsePromise.ts create mode 100644 src/core/fetcher/RawResponse.ts create mode 100644 src/core/fetcher/ResponseWithBody.ts create mode 100644 src/core/fetcher/Supplier.ts create mode 100644 src/core/fetcher/createRequestUrl.ts create mode 100644 src/core/fetcher/getErrorResponseBody.ts create mode 100644 src/core/fetcher/getFetchFn.ts create mode 100644 src/core/fetcher/getHeader.ts create mode 100644 src/core/fetcher/getRequestBody.ts create mode 100644 src/core/fetcher/getResponseBody.ts create mode 100644 src/core/fetcher/index.ts create mode 100644 src/core/fetcher/makeRequest.ts create mode 100644 src/core/fetcher/requestWithRetries.ts create mode 100644 src/core/fetcher/signals.ts create mode 100644 src/core/headers.ts create mode 100644 src/core/index.ts create mode 100644 src/core/json.ts delete mode 100644 src/core/resource.ts create mode 100644 src/core/runtime/index.ts create mode 100644 src/core/runtime/runtime.ts delete mode 100644 src/core/uploads.ts create mode 100644 src/core/url/index.ts create mode 100644 src/core/url/join.ts create mode 100644 src/core/url/qs.ts delete mode 100644 src/error.ts create mode 100644 src/errors/BrowserUseError.ts create mode 100644 src/errors/BrowserUseTimeoutError.ts create mode 100644 src/errors/index.ts delete mode 100644 src/internal/README.md delete mode 100644 src/internal/builtin-types.ts delete mode 100644 src/internal/detect-platform.ts delete mode 100644 src/internal/errors.ts delete mode 100644 src/internal/headers.ts delete mode 100644 src/internal/parse.ts delete mode 100644 src/internal/request-options.ts delete mode 100644 src/internal/shim-types.ts delete mode 100644 src/internal/shims.ts delete mode 100644 src/internal/to-file.ts delete mode 100644 src/internal/types.ts delete mode 100644 src/internal/uploads.ts delete mode 100644 src/internal/utils.ts delete mode 100644 src/internal/utils/base64.ts delete mode 100644 src/internal/utils/bytes.ts delete mode 100644 src/internal/utils/env.ts delete mode 100644 src/internal/utils/log.ts delete mode 100644 src/internal/utils/path.ts delete mode 100644 src/internal/utils/sleep.ts delete mode 100644 src/internal/utils/uuid.ts delete mode 100644 src/internal/utils/values.ts delete mode 100644 src/lib/.keep delete mode 100644 src/lib/bin/auth.ts delete mode 100755 src/lib/bin/cli.ts delete mode 100644 src/lib/bin/commands/listen.ts delete mode 100644 src/lib/nextjs/hooks/useBrowserUse.ts delete mode 100644 src/lib/nextjs/server/utils.ts delete mode 100644 src/lib/parse.ts delete mode 100644 src/lib/stream.ts delete mode 100644 src/lib/types.ts delete mode 100644 src/lib/webhooks.ts delete mode 100644 src/resource.ts delete mode 100644 src/resources.ts delete mode 100644 src/resources/agent-profiles.ts delete mode 100644 src/resources/browser-profiles.ts delete mode 100644 src/resources/index.ts delete mode 100644 src/resources/sessions.ts delete mode 100644 src/resources/sessions/index.ts delete mode 100644 src/resources/sessions/public-share.ts delete mode 100644 src/resources/sessions/sessions.ts delete mode 100644 src/resources/tasks.ts delete mode 100644 src/resources/users.ts delete mode 100644 src/resources/users/index.ts delete mode 100644 src/resources/users/me.ts delete mode 100644 src/resources/users/me/files.ts delete mode 100644 src/resources/users/me/index.ts delete mode 100644 src/resources/users/me/me.ts delete mode 100644 src/resources/users/users.ts delete mode 100644 src/uploads.ts create mode 100644 tests/BrowserTestEnvironment.ts delete mode 100644 tests/api-resources/agent-profiles.test.ts delete mode 100644 tests/api-resources/browser-profiles.test.ts delete mode 100644 tests/api-resources/sessions/public-share.test.ts delete mode 100644 tests/api-resources/sessions/sessions.test.ts delete mode 100644 tests/api-resources/tasks.test.ts delete mode 100644 tests/api-resources/users/me/files.test.ts delete mode 100644 tests/api-resources/users/me/me.test.ts delete mode 100644 tests/base64.test.ts delete mode 100644 tests/buildHeaders.test.ts create mode 100644 tests/custom.test.ts delete mode 100644 tests/form.test.ts delete mode 100644 tests/index.test.ts delete mode 100644 tests/lib/webhooks.test.ts create mode 100644 tests/mock-server/MockServer.ts create mode 100644 tests/mock-server/MockServerPool.ts create mode 100644 tests/mock-server/mockEndpointBuilder.ts create mode 100644 tests/mock-server/randomBaseUrl.ts create mode 100644 tests/mock-server/setup.ts create mode 100644 tests/mock-server/withHeaders.ts create mode 100644 tests/mock-server/withJson.ts delete mode 100644 tests/path.test.ts delete mode 100644 tests/stringifyQuery.test.ts create mode 100644 tests/tsconfig.json create mode 100644 tests/unit/fetcher/Fetcher.test.ts create mode 100644 tests/unit/fetcher/HttpResponsePromise.test.ts create mode 100644 tests/unit/fetcher/RawResponse.test.ts create mode 100644 tests/unit/fetcher/createRequestUrl.test.ts create mode 100644 tests/unit/fetcher/getRequestBody.test.ts create mode 100644 tests/unit/fetcher/getResponseBody.test.ts create mode 100644 tests/unit/fetcher/makeRequest.test.ts create mode 100644 tests/unit/fetcher/requestWithRetries.test.ts create mode 100644 tests/unit/fetcher/signals.test.ts create mode 100644 tests/unit/fetcher/test-file.txt create mode 100644 tests/unit/url/join.test.ts create mode 100644 tests/unit/url/qs.test.ts delete mode 100644 tests/uploads.test.ts create mode 100644 tests/wire/.gitkeep create mode 100644 tests/wire/account.test.ts create mode 100644 tests/wire/files.test.ts create mode 100644 tests/wire/profiles.test.ts create mode 100644 tests/wire/sessions.test.ts create mode 100644 tests/wire/tasks.test.ts delete mode 100644 tsc-multi.json create mode 100644 tsconfig.base.json delete mode 100644 tsconfig.build.json create mode 100644 tsconfig.cjs.json delete mode 100644 tsconfig.deno.json delete mode 100644 tsconfig.dist-src.json create mode 100644 tsconfig.esm.json 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 fb9f50c17f5b0296f3bf1e2ad960876e9d64bb55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32184 zcmeFY_fu0})IN$>5EZa}M3ADOBGN>9OGE{vD=0k_=_T~uq7+eSB28MNA|Qs)i-al& zNDVze2t`V0p#?%o`yPD1bLaj6_m?~KW`^N#LiX8v?X}l>*7K~qd#b0wc82>56B84g z=99+;OiZUjnV6Vwo<0TKkzS701Aei3J~8!SV!Cqc=;xTG!OczJ<}n`wjfYIdeLTy+ zA154Dbyb;|$`a2~ZB8;Vh3RTOR(&3LY>j4BlzjjZ+6NcNao#vKVR}LR-NmeVmnVB= z>a3oxq4qcXi|pzKJa*0sR^df`B;(xVnlfT}|ALXX9wVJ`_bhtcsDwRR@duUo!Hdos z)-}?nZwlaQO-?`b50H>+a+p-@eE(T93v&I5SRXtfCltRh^uq7Q$^q!bZZ;`cvw%oG z*c&#~S9!4;1V&&2h&cgM<4(YIf!7kMfct;0f^}z(|GVnYLCo4A1sp;uLc@hxDWL^Wt~4S;g0X=5 zMa#ftZRGgY#lSE>!zyX?&aIR=mqA&b`z!Pevg6SS>*p_2%BM>qhulk! zcOFq#s_r9oMN&1gunSz;G8}SOrI^-puyv(rIK!#$CaU}J_C-)!fNGp$Byqf zQ%Cyi(qy~u3R^Iy6NLDfn&_)?vi!<0<$VfgqvyH`M>f(JM%>z?lVIU5*E+Xq z!dJo2aH_|~csqi=%)l;ofWxRB3XSwih)Z0PD2~13k>4B*piBd!Y#csMfpjzB(s@KOdpY_N-U30{s&oED*`@@ zsj(Vv3PYrqTr1{a7xDYM9ny{UNN3tu8i17__#{ZXUD%g4SEC59%xy9_C|OMhs~h38 zn#}lk)XP2PUl`9>m`G0OY8@}z20i5C2=OBm2h2k=Qlk^Y3wn3^Dz~3E4||BgPmJvR zuN0PWkes+0Y%yct%UBGUI-8Gjdt@~@z7`W|XVrAL0t^2;Z}oAFA8=)pF-@Tx*6Vd^ zP1*N+coz1%Xb6hWJo1}U?C>=tHEtG>17);u z_JOzlh51*SfYB~0Lm|Tq*T`VQXUrMY+81ET&U2YT=hWC1L(1WrBL|p1&g%bC-bs^9 z{F?_r?CS?*x6 z#!3upM78)&56&*)6=sAkm_cH+USe3G9omI+&XV%$7*s?P<+G-o7JC=ZcO~A=<#gauq${6zCSSiJREY^E|G6}s9ubOTA>0I0Duf)k zt*YG+lu0mN`IiNc@7smnR^%6Su=ho)$1z&#m~ACSZ_@!*|AbgjL^mOvhV^I^+UzV# z+MxXwB6H|9Dww*1joz?LgUxf4IfdYrjtWNQC0({I`&}{A3?V6K=8?y(*;(YQ9p~Sl zxvry06XA4CS2SJueCvaz7;^aYwjYpjpAgtWyX5P(D`HloZaHa&l~MbAM}B+mX*V)R z2nOPmOr0B}zG{5%Rz*V=!4Ppcs|uCCZ3}|8mup=rT6Ir0ZJuQuZZ$bJ+2d+9NI~bY z{S^}4{OR0eJ;81A`vUG$ESk&L|3o%X8RW#v=((N@nB+b~t@`~!)IFZZsTt98XJ!^$ zFOAOo$7lL{dR&w3_NEp7g2LJl`{MN=dvoj#O^aWc|lt7=4WCN7P*)YwMbkib;W z!nJ~&d`4V()l@%zgT5xT+Ed$GOI+|D{uq|d?O*5^e$dS*RGpD}%D1oZr20zpVT>TS zOJG~`42cnIVSF=pa=TfWqw9ns>!tRpjnw4l)g+B@I}sULE;zW{r!eZ_f@&Fr6+O`x zJ$ZK9?!0DlZuU3M5&R_%es>kFI5ap0$J>qlZ{a3L z=N&7=)BF>Ub020{4Ciz{Ke+mz9ve1RV?@MoS|_(v8FJs=%7LOD2FtqVER^RjFMb;5 zdIF@9RE@razZ!I&t%M*|l>MzM_|C$y-6qC{K0M|ABguAJ_TZNEkJvkc_3B}3FK%5H zwcY+`BvD=`)J5m7Nf_Kreq1)PMJh-2s>t*mN5!>uX#_n@yxT`0!L~T{;rGHf zS-{la@2*=_gyD1lp0$$41{?itq69E(?Ea+S3>S@xzm})zXGGWjS%=&3?>FzoQTLwd z>)j$sbaWYQk~o^`md0BPH@Z2(Jy!z~6bloSjJ(^LrC*P2USd94`kEc(NyQ1#gPN$@ zz^Ci{IxNnE?^Ryrj($qroKsvU6UD8}TfW4JYeP%iD1Zx8 z%j{Nf?-9G&7?5ER$8kA(C6_K}mn?Ofv1cV0lG9wEVUVKVZ1|oOG?Q>9l)jW$jehk; zqwJPgU*`?phsjs2Y6nBwKmMoia-2KX%_K1$3?d=KF#xc^{}O3 zKo+e|@b7{Lh?8yJgWNLjQE^M}vmbPw z{yEs2ydfde$jYpQYuCqwV&K#Vk_D+K&FN(cc zH90`fW*V9ne~Ee|sl1Jfzi(9RyXQv383hdNg>SG-k(3Nv z9R?TYZ8k=qxKdo>UdTy(W(1$G)Oo2vXrSzD>Fmkc_V|$Q*+cVsj*KXxJu=lsq zdy~tIcyV9(t9z=bVD_J_B-*npdAQ>2{VqDUsm}?&Od#eTbrd#sdas?IfC7}y? zj^JxYR>c<#cY41jZ9b{Ib3;2KeR%T?cJyG^s%ZS_)c=TffdZ7Vvg&YNlRHG*ZuNPauR;n~IQluM6U?W;wi_lh=1KoXd*4m;>J zKcgc&%HHSxQc#UL=eU(o$`mDW95V zZS4n7H~-lX0f!Wnw*Gx<$j+IzV_T04AO2YnUnS zprpMc*!A=pW6cqF(%kvU7^tCevhU)b6vdtIiVorGg&PjggPeKUw#{&SH{2y~vhzb) z1u7a%_g?GR#4-S=4y|y?>$b5lGv!lOc$X?;-=)F)`i`oe-t~popO_h?m7K-i0T@Sc z-7b^figJ;t-4zl%igH(|Y7$X9?cMeui6VEy{-&CQ5XqY-PSHrqME-}i+dg5ck%q{U z2;cfUH*F|6~-=iJj9GA*;38hQREE3xKQjCHl1R)(dE zTcK{eS%iR0ku=A=An*O$&~+b%@BD_a>q&d1Zs`IdOk%fmJLKyyQ;oJ5%B_3z@^=Bs zXX;vLuV+B7r&u7|ud_nMz6sKB$d9<1l@1+m#26Oz?ai-RhV6pETNUNmnsx~NA2N%C zd~wr82`Q)rj{M5%%Cam&^R+;;R&aaK7iYsHSZZM)(JR$KJHT^-nBXbWpLmU7&f) zLX?zi_q1{XFJ92r(tXENeY9PTJnu!eHGt=!btfB^n5;KFSH^8;+Cygpe-V zlB<4<`M@c<-;tT`^?Bxq+B|ZxF>TEsR+Hj&{>lF7)t&A$Zo06;#En83I7kIOl~E#W zqaJrgyX)lWfoWl@!$co|x;OZWj{`X&VECp8t$Uaf#$oR-VmIcASoyGMOl0}X5Vi(t z(eSOkiOK>mAn8gu^CsW_>N!f*W+=2WX;m2Dq+-U00v?E>jkw0;jJl4ILC@L+p${~vM=u#~*JQ7Ri| zA%vU&S#GE5i-XF4wwW?6i=r~WH7=a^SgI{wIWz{J2X?eVWa)f7I;cr#_n7#0_ z;)zWYaYHXh)DPqA5|cM-BL246)U^krd-XoC+Sjy%xb5F(P{P{?Lyx63sQ*7~j=s#% zQh;JB^MCmuCZ_)-y#B`-{+ApC?C}40XRzi7rVuqE|53Tt0fAP>OIt2#)$fkBF8+#S z>7`LPGBpzU*ywODsg|nH_xd;`s{j!myjccg(0_IOid>CkSXqI`)^K~i6>6mKye)Y)prRg@qZfiyXPl7<6#P1%3AGaW#HyaxXwH>RmEq?hb@B`H(AHc>W@}91=~qXiSht-HeU<7- z=*wAbkI#zy>}Y`1!&XNiP}XyQk_3D2c7n7@eF%N{4!mCXN1E^U{JX`mvbmOj{Iy}gwbDwUMIUShB8x^0tU=UXO^naB1 z)UC~9SBg1QF?aHidEJ`@QOe8yX8xR*qH>QD;Gxv=o z4;G*L_0PPb@Mad1w%-bm~zYBn>6IUBkfb|hCY>D=Bt zrkKp0c1cKY2Q>C)LWS#D8W=aGt%@o2r2rfuRB-Cwu#bRYfnvCEghwT~2rD>wzmYgu zOX`i4fV)rp^qNyA4Qi(cgZqlvzWkfFulCIG=QY<_;Jze_rf}%_=RMl}q;GM4+Dks0 zGr|AFgJHllKUPABz$ndap#PuBWGeg@p6 zj%}z8#!v-T#`!;{Wf#Q18ZCFnRA9&+x@o?Vo({q4N7uxb(y*8P=dhQ>4%wP8@|l0Hz5`o6 z8&h~yPR=7c-K#6$ex)$k5K<6)6*wswsspfb8+}Dnc3#E$i@-%tU$%nEQh4WB1#mLN z{1|pL#H7Y@;}5Y_)qf@+4a^?zS;%l4MV=C1J%7g%WM8U+i0A^{fY};>*I)xR;ks+fMMl9J4??hMC~DPz={$L z-Ok)X`tYk%Q<0s{M%cl^wWD+4{BL{5uP&eJ5zXPP`W9#e-fUxED&{Wvue5g87nV8)$x zs1kpeICAbt@MJn%3u9|O=YlHp-k$Gt;JWy>e#f9e#xXzj-h4|mDbo7e8(%X#+ApLO zKDasYh*f(koH8&IAcpq2v*#SYoAW=C;5{YReI}`cu?{l$!o40r*S&Au_DL5bvca(8 zpo0bS>))b-ohij|>iFo-4~{8Ery@(vgPpu9Fv6Ye$onrMe~M3XZyvtihk}k>is7i? z06T1L&YDMFf0<;X-(CYj`xXZ6j^I!&@Dndghi>CwJ4;)ODk50y)b+c3l&i!(>v|nSQAb`p}-mk@ZP=6 zjcMwBKRthZZ!7;`>2PM_8h=prv8A)GIqp@N;RR&cvvhNPYloM#GS%A)Aa(xGMO?b4 z5U=XO1~d|GbHK$KR&3QIYohVp)in2xsxb}<+q5ndWB?npRJ)<CJW|!F`sOU=gY9!LQgkAjrl^GS1WDDXyU_`+W zd{#GDxS6aSr}x5$me&Yh;TDq>Kj2~h4I|OEL6EJ(Rq<01FUrVS64JeLRv{Zg7vJ!} z3QgZs$;lmuPd7~#h0XoCzEkKfCC?qD;W6nxD$59%lM>2Y9Kv`+gPBBtqCy9yj&1we z0tc#N>DZ^};E5Bav1bY_gO>+qFvX7$7f)0st&3KFTXh|9mDIH@fOgH@NQ?zSsp{#U zjp3e~fj|rhUQ>d}l>0c$DaAH9ZFu#Q7GGjtK`0Zw`Qy~9zI5vE@m{8-gHzTofZ!CZ zwN<0N{89LqIyQkHEc-LF9QxJ|aQNQ82Ur%|RFzs3NQRl87eq^^YyycQGI8)r3ez3q z8~jTC7W+W{>ajOf#e{q#5xOPV)&D@LzX|cd$BkMH*_u;a;(U=8PQ_JxFBK9T1ETnF z{dQNc3$Hd8OFE+Ftw138S^BJjY2tUb*O>K`K9p~TeWms&Rx1EVLCw$0)iJ2Ey%QxW zHPlkaCyL?l!v)xI5x#DUNi>U{-};M7)UjOAyK9=)Nh`=2CD!6W$rSdtMK#IAZ^iAc z>~9)#lBQgVSy7rA*Vp)}kmI2qjH&`?60uMfL%v`)yk;s@Dt2yA?6`qZ0OV}LZZiR- z(=!`PhLq!}=w8kJ|Z2V31PVa z%6KK@_C{HsHlYtadEapC$=F9fKiNm{c*lf3{>IR`D%H)QP-3bB5tCncM+y&G1|3om z6ab#Q%DXqn_Q@<>(!8qW)yoSu`dqXA2itAYC(p-nZ>9AM@v6!AjkL9`r_BiEPF*QW z`wx&+FG)qITtmoDx=|-Lq_0`+S2)b=Yxk-@L|n;{y~ZErzbPe4U+|EFF%H1avI$Un zkMqN9UqSn1)4wZc=Qla;fRqbJ+u9(nD-%o z1XP|!B=fewqwWDm97}ur&$$D-b2U8VWJCql zI)fEiEk?1}Ec?#4H6eTJ>@5g!NJB8n%(1OChU1!(;Ej181R;I@J9>aLbj-&|D(b-4 zYFT`VYgY-jy=K&b5#rFNw+MS2f1rDG&XM0l#0IpxowAJ1_F;HUbp?vaTnM6e}RP$S*Z-P+Jv+-KL+hX^%P`tKF30)1vn z0Do_oP$oorC-|p%CGA=7U8AFkhK9DyzSi=mQ5XM=QpXbR>Z_b46_fNV4rGm(4IAi-a zMrh3$gsvZ0-Pp>wq!a^c3K$OtH9rJScHziX4_S|=A0swM7{rpOQ|Er^31NvwdHl<* z?sGR2coZf>caZ^E$63(VS!MhHKg#HCpavQqq>j&>DdVh3hf_kr&$hz9PcT8+Byeqak z=@xEhc8(yPS$uKq<@Y2>s46%&J4$5^cp6^bbnbig8U=vUJ(ccH7sKMSg(j~ODLMd=R&O+-M8mzw;LSt5r4kH!pB`Ux|?+!wDFe1MVHY73j_`&+iU z=FAA7S0do3UOED$ito{III)V^pi3Z=o-v(fFB|@sWDAxv=_V-Q6Mml}iyx)&6E~sl z!g>2jx_>@;J#B%H`|%#mgEI2x?&7>ii@io2TE;c6+kgse@;Xz|EnEB%(dJ0RS@APE z;n6C`lhL8Ft?(+W6)@-Po5vjPd)xro=ocSOYqr1v;K(+*@*OG{qoRiWf+;jCC@@3Z zYGSlRAE1@IuN4iIhuHO)gIxY(8bGpveDUfQr9H}>p189U(X0;!(|tUAr5gDj%pC8r z3kxH2nz5RB=P{)Fa+Kumtc;Eplomi$m25giN+#Fr*izkam9H({Q%OaH+&ag-LdnIF zPq#e*K{wc8@NEVB6zCd;2b#H;*!and*c-z3fO87V)ME{QNv&KZ+r@u!R)5p$GTm4Z zGBR$MTX(BQrbb~_hsYenTzeEEfjEKNawW-Nlq*1nZ~gW+XW!rnJIg~^uiYs!VSLIz zB`&+o&bG$%;_Cuf2+@O*GCzEN_)aVc5|U5&vr@5G4)(snr`idfMVQpNdmRg#k_s!e zwk14St&{~ijfD?cdeftc=0pl2CHM@>wo>x4bGsVHi|bjs!#xvTe)UL%O{2F|rl)rX z9mXoQ26j<0A=TQ=sW|Evywd9vChWjACJSmL^z7^o72^O-AV+VMxNJT)q&{WG7Nhom z-Q_YKldT}NwW?L~tlGw`g3orR^{hm8(Z&5ruhmght{PYMN^YPIW70WZLD_g;H?eY2X0Bhprj(gcMmu+RZli2~lP)^yF^4=}0 zY~`SG=T$n564_t2C}}!BrgKA5(2hd3_}H;R#MPW~O|_pM9+L<y6@ zW^u8BD!;_H>3oO^@V47fy+W|qpzncOC;-2XYITQ?a(9Cwm340jcsS&9Eu_gEJ0c~} zp2Uf&+V=k4f$uG9>m>kA_b(SWU`7M9%bLmeKCcg3?#3nSlNTfvg}tK@mMR%O>eYqG*O3%c{GQOtke6;4qhKkwnQYTS^_J?kTcO}~OU<&#M#kN`!- ze_4FulsW0iue-UD2mmcBgpflE)R12&`}ALG%&``cNMNC9zGbOC?V_q}_2uA;jYTW;Pa~TL;x$Mk>gOSFog{VD5cc-r=_H<`eq+6nY&S7u;)~p%PE1IsT+g0efX1w&I?9LZI z;R$hVw2r||&1YezYM9LEOm1T(M_&`Hw-PQY1ftCF+YND8e#|ZY)g98=Xn}nGSX6B| z>T{x7ZCD$@fnat0A}RSq@9AJeXX7{!^)t_1aG3gM96|HcovxrOvba;-f@Oo){)X2FJ~p(`RDzh7iSQ{!{4rE zy_~4`ZgdK%w~K#WY_Ae2+a)jq3EBJ%=`dYSbjG$^6K^lcyy#mA5p>~0US#Jq=XA4h z`A7Vm92w*qQFD|Jc@(s6nMdgTdV;t6j4i1?hqQM$J6B*s(%VXGP_Mabrng!%z)Ayj{_r*gA9NlO4C(0p1 z2_JE>7o+?n7v0ka+>_t`9NielyIfv^hX-3#oH3~HRVaeqXoxrXWV$FDmrPQ9@2nu# zW3JPOJ_YqHyWQn4^ZqF$(a#kZeYqn+cdvdR=4B`swE9nk%X*};Q92iUVlpj>*(d8& zwFoXTSJ}vWdU?gh^415-)CacNQDw_y)8Vqlt{WzO$AiQMunCVHDz?5PCNky$FcDVm zMb^)ZhaKR0+hL5X-gbRh;rM=MZ?mJ++W7wPz^Tu&K!{EEYD4&b&{66_`K#Ws!2s+~ z^{F2Ai*HBPKmN&Tt(U}Wu4j3@wul!wG=<9Wk%s)ahN~Nx21h1rhn|f29_}ow+Bl5d zIZSsEYH{ejPNTWZ(@l zU5Hp24Swp#{mGd18^t@}F7G=?z)Wgs0mqH8O3qIMw*0)Xt?7xZ093&7d$*2e`Yifq z)aju(gH@LlbH&)S7w6WKqwYdZ*b1-d`Ba4{#;K!DdI9C(8$3#VE6fyP-FouP<@mHJ zaZTZ#dg}HzRxQKm3|=@86U-nqx`)75%0q6~6sTWlS`{$IiGNeB+1UB1{j~JG;H3Y@ z4zKht7^Bw*KhGCMfc{%CRZ+0oD^n81h5daTa6@A`aHae0Z|@85e13_i4LBlrnjB^( z@T+W0N`U|SaE-)!5hr|ewa`}=()zyc`pq@xr&<@q4g?G(ApUGjg;RUVuglYtTS9Ph zOGxiHQDiBk1^$9(wmNxoZ=7uU5Rol7aViwDj`VdO{tz}~zceK4q!JpqJtt22l4zr! z)Gu^463eKS3m#7z^gAsZyPcgB=G@D-m`dpLeY5kTZx-SP3wP&lO>T6TliL6Nr3_(K z7XV4;Xub>pS`DMapighUJEp;UBh1VXl zz7K3~&8$Fw^jReq7D$|T_xFtM^#7O_Q$H8^V;{GR>G)I~0%TrLecA^v_d4^3AGvwY zVN|g0y6LiA=VPr!<$#Gk16f z+SGWuKfZK{ue$HAeLBqt5AeoW_Cxqb*Xow{HSa;@f(m@kp8aKF%C$;fCUk@c6Q`;i zh9uzlOHdS;EG$zwaPgzSp$qnvkkY*Xu?FXzlNT1&J$?ehucKTWwtuA?jw}O zg3&&p1+5Wsix_DbzZ(?T#GH5b;JX@gb+%hO%fY|)b3|Pmqz@$$Tg4>?uLdNnc%yzf zYY6F=XGI*E-MET$oD;&ZMcxk! zPrKn%GFWnSH;#nuun9?7V`PbOu2>7{< zj)0L7zJt{30IHOVj9AvYVFkDK))`*m6Wy-Zmh|`&xNJas=t9=!%dM5zmhYV=c?1ZF zh;_EIN=zG6=k;wpU)`dwRjm40q2L1QXRbgjYLCEFSzDGP+_c2~43<9nrhbl{^T49|<;cx)QPBKTJbN4YT-mNBHzP>tgAuBHAKG+SOxiGduf&=MjMd z_NqQ+9h>C-fKUvnGuvegnfhGPGD5G@ow_r^^XQqaGCS>K-v^6bM2jA()bR!1hC)oQ z*-b7!ZJFh{=GVsso0`aDsczb5#t)u&*-bF(R!c_zzWmAHd66^V16=$Q>b-uU2A@}6 z>R1qqzf^-IC}9?R_NXNPpt$+K?3xeU<9(Mmy5II!Yo%j~2767k-*?yQlGf5E9T&FD zMEakk&)#nz?)9N}L;GAaa{B5Z@u%!$=6N8i2U3ttRuzJZMrwy0>VZIFo9{u--4bCJ zl?2k&E;R@Vp1U2Uu!jxSO*%W8rV{?q-Hp1Fo*AFGwn^VnN<4)J>C?8sE~AZPt)Wm{ z8I$$5YyE}w`^7Kz#quFiZ^svOp~ZA$9x;%|$ebA-xAZ40``N>)*cstJ4FJnHr95Tm zr0l;o_I%FL-+(fi@yrH##^~95BNh%m9t)I3uBS9nK|t+`Ebm(dkX5J`T`y7yW`y4j zdB28He&cd-Bgm~f$u=n4Y<9ReOO9m@=JMp2q)S|8H%8vu?ht3TFYd}ACbaL`T=p1QeGF&<*&0)iViRQPYW>_5iFS}$GNo+C_L?;kGJ3K15aEqS zyxJ*iiHrM8IX_h9nndHYyNnc;QM^;@aK;-{~N4RG-7c6ETgA`30etTk?>y$&uBSJymcGSS~ zY8!+3+In&ulR_WA^3Ul*P*-C&C|mYAs;bZEeQFgZs4AjVnSL>|xJ8i*+QC##xi#NSCpYEfL!;UZTfQbr~qpQsmB|-vViNxh<%5OD?Iuqt>*lubsr!{A0RR}hK ze&|aLg-Xp#Qcb$e9w3xOsmI7XuQ~cNE*HD>eUR&dN*eT_YQMV_2lok-k$M;f35{aG zIUT`@Ax0`|g+Vg((oEyOeBkh+V6WR+@)mPi3(oJQ^K4zFUOA8c9lHsM^AQ0QYe?Ru zLgEO~UUXDmhOCP2Lw^uyukhEv-P>MB_t5dP8eUni^<6e_B-CdgKhj2!BrP(blJZU4 z{`WUMkK2V?pNeE|d)L+X^#$b;gQQDF3WD^yHww2DO94IW52*7n5gNJdyvItb^rGhF zcLvrRG5xwq)nX7Z?*FP0K$RK#hjh9$#I%)VNhd2hSVOW4>g6Iqtt&cU?C3MU?}^@c zG?t9qa*a`W=4xm1o(vKHnJBPe$TzhQ`B|?0|+Vc0w}f4|SouA79m= zoH3-@r5`A9bWU|Q#)DG-!6#+!S{)9EK_0T<>or;Nj|ws~!sMu?lwQv2QObc82v>wZ zBge*>T|52C02Wi5zE7O4g+d$Ker@Rs%R`sRv4DPK-vaAbDgUs z{-5$`SSh*&!bv>3g}{iy2hBaYwF0IZ{p=42lIwv!~xLomml+M9=UaDLyaruODNdwvXo_CRc+rs1<@%Eqp zRjJ<=yMMFf+2?mT?jgAAU!i+CrYzrGp&eyaVw+-rw|>6eY=UZ-tW`?j_&B;xV<)a; z;P?ME4FI9k|Cwcw!CS4@jYhA9ARrlaV!wXvH0d^VAh(9) z>{~ExXHO0Y!ET2)U9)^m1B)))5zUp)WbVyTBA!{ZSML4R*RsZtrF`#==z}Esh?Mhh z%V+E#uXNJoB%PdRW!{LRcM`hB5HxNETkhqr6ONHrl(!i#|7it)w`EMu$5PcJV zcqKio_8K63UrVmuvvLpUD_H|{0p(%ogST-GAvE#~rm9+SLbeG;jHedidM9S~+#1t# zN_Tn&@`IZ8s+w1TeR=^{z;w9BslB_0VP@Q&D$d`Q)lg{hPSN7klN&cqMn_$iDfTCt zrMqTT^0vJY{c7C=F-`nT3=(*)^KjN{;tFzO3VP{EFFiD+<;6PIbWeNq`lm|%+s<{T z`t@F$p#EA}O86dFd?4y@+8D1yRp5(LrM2Q;x+o&pc}#k$9Yg%~?jhsipp;NwfH~@W z6dAIIUgbu4CAHk|duD&rG0Vrzy{ndP;Tl=b$S&`u8OD2|DP}#qp`2 zj`FI8uOSmq)H9u4A6359Ft)G>e*?cTP{uc6n>6XvN7B+Gf)KJ`^rS7&B=L3EHq3Ma zq(?%-OgX2j!Y~7bSXPr)<%<}7E3P7n)ah5@iwopq{ebc~+DQF6!(ze;G#O>e+WJ>_ z2cJiW7-x*04+ou>O#AkS$btJGZIIt+lJMyG@}kWlwIOw zd*_r1-(xu?$l{NkDDtQaYZxT)Fu^`Y{*xB-XMNtV`$r*%gWpwl(G;PvdAx|}HOEsm z%ja0`kPi=kpOvWOXL}6c!alF)F2LB{H;LmBWDPS~SeYn3n<(S%h!fDfrX+S<1(id7 zWEyE_Ny-jcT>dG1l6E2*z=}a=zlm&P$J)#g_<7fCW&R@x{@&o$+PF{F9*i>Z1`@Kf zM9fFogN$6Gu(5aZ(fct$m0|EPaX}<0cz(EW=*4LJ@`N;}{+eCJYyEhNJN&GS^=Dru z+FsMRTj(q%Gaf<7IY7f2uThJ_4hSqT7L#v4Jo<0GtHw+(TYj01{vPd5oS0$rU{i2N z;BgL+QLM9ie??&t=rE|>IT=`|ERlE5&=!icPk8tK^4p0k5!1n|vx~5f0C0-m`*QM7 zD42ozCEItgrRUPOb5=I+XU-M=vH-jE>uY~^p4|s@u`;b? zygs^yXrTPlbJn8O<*tXj>*a&4mp{8LeC$e&be;mdu(0U2SD%=zK`u?5eHgug*%4z8 z36=gOt$5^u55GhRb>OQai}#chsXqB($ms1X^@x<9GxM#4>L_r<6+WNvo{bzbC?^1q zcsY9E2^T4dpeJ|3?F{nZYNKvmX(pESz$c7m4Rl0#C+u;r{&%~7m4yB)gUvDLinyoVX6+{q2F8!qK)Jq(7Jmkr145wR!#n^ z4~If2tG(@8FdUOF@D=<@qYC@RMMX*9T!$nHHRIR%d)^Clfs)^SBOAh(?H~zewy-Yd zjlFd|KGirC%XOiOdap=*MoKryK_vT=cq!Md4#LiB%-Vk?+1NLcncIwtQyK~yaOEKH zA0-XAulJ3MjSym33n3p3C@aSdI5{=bY|$cRl=TZBm#^(*zKQa=p_v-VxiPY503Ct4~&au zk5{JEj>^ol^-G^D=-J;>&u{d#*O5Zs*n5VOL)Jc=zE+MGYKy;YY8gWO^;x7~aYDa9 ze=nf^&&|38uhQ=cu|dr5Z7QNJlTHZdKFkbKzo@6ADB7W)C231a(C+!(W`C;GUghUy zUj&S9M*_;_$djWai?q@rWD=;Dr-oxG2Iv)&*nRo-8R2`8F_}sdlC?TMy&0bWsPCDm z7CQW>2fzchE7hh5cAXCFFFI;X@MAsZ3rO~rAD_upSw3}qy_!?Zj=7dw?>vE0o3=;$ zF8{s$DkJ~A8S;{dN0b#HO17@2ehrO4#HAu_ke=RE<4~6qAY>`oG(O3D2vpEhzS!fR zciMP$wffqB>b;kq0sy&~c&I`u1}E9BFZO0>^WTye{bfuxe~&!k`&Au3x7=ORRggQX z=TcYBiO<~ylme3cx8m^&knj#mLd5+m(DT$#IzYe9Nf5^xq)vyq(-PSp7y~^EB2}a7 z?<}Hgl7WE7PM8h ze_9wmnBHn&u0v5b0c#73CY+gefCvhCXX_iDaBB+3P-jhx;fxhyR)4f!@-GLJZqgoOMMTM;QWQpXsPNwiKHh&4 z0MS~gaT?H0_vuHSqr7!jR<5j|+t^2%qH>zYup%oi?|T+=IGeqVw|~>+H$Hh#a|O#{ ziwpbfenEXshRsFd2KctTS=1!rNOy^Q?qc|S zH#;x#Rd}buUCx~0VB$pT#is&FCZ7Rvu%B4LMb{}Ohor~*A6L8qP;hAi&rxzz z$G)}tK=Sv#WD68>b5M^wOQv{biX%h=EmvK_+b9i!Z3`p2pKJgdyT*4ny& zkQfw^qM4)21yMxipsyRAcOzLSyEJ?W`t~{VLBROxp^hL0dHDrImm}nnWrah%n^oVl z+EU#Pjve#%I^Au+dT{|ddhrX09AC0^)U z7dj!D337w04J?;%+sxLxB1`Lfq;8}BNJUnxR;fkTV8FAXjwi1Mu&Yyoce+h>S!A6a*~699zX$! z$i0dg;an^Zh=XO*1q%%Ou_jN#a+nEmvZH<K@ z{UKgjicw;bq1-6^D;h~FNdIc2uBP*mc}#E5Yw9`{t>~Tk>K#5;*HOJJOR?3(_gqm+ zCbtZn(}C^!MA0>=<*S)OPcjyN!X3}yzmaUS2#rDNJ-q@Qe(!&UHP$m|UpQ`I%Aub`oO>CpGW{7_Qd&LJ6_Rprq*TGS9!;@n7V ziG&Ed9_m~rR+BqQq?u%Z?v78b-?v;yPrkSp$ayV4&dC3eG2kwI7j+o;#$H=e#N%qf zUG{mBsX3t~y7N~*&{~@boL(8Iea`#1!?yHn5ufr=OlWjmmt#2)ZRYUQBG`l!?GP=P_mUHHOYBgt_`I<)|)I z-_4O3FIFx9nirPki=Kt{`M4rt%O94Yw5gZ43-Y7>LPqsEhQ~*%0`BokOsfj zJ}w{n!?Z3SY-hik=j_4$2gOrQV*rUu8|9wq++M3|{--@n_0IHDR{-5P|jzN)=;p5J&j{<}4r-NMMkfzZQR;WIVsay0FQcRoh5)>wW zw3GhI6UsJ@edD=(iBLXp(4^?97J0VCWuClHZP!%rGc&Pf+Olsen;5v97b|$Y`m6fW zipIvNA4+U*P~M-*;|S5*O%jQi?WIG-W6 zd2m!##~aJFerTFF^SB(gO_&C`98m`W8t2PvJ!C;7s?n0vLwCkE@$0WlkNSe$;L$}I z8Tn%=a-|hf+T+C*2klO#t4?V&)2SJ~@*#nRihF4u9)-`E$Hi>jTrPjmW~$Z^j;Qhj zbpDLSMxuh#aF8S1%Y7)prJLL*HM#~6H}!<+K`w=o;*MItVPR*GtfmIPsfXBUZr`;= zAZ0944}4y9#OMifD(RdVa&i0DcJ{O~jQ!4+&AVtAjJ(@lZSF5;!TPE_CwIG6Ede>X z0^)|zt*j8lAm5Z1S1;8Gg%t>uxQciwdQv zBqT8A_jlJ9$ z(_OtXd`AF#+G66nB-sJpVwvUB|NZN@^6I1B`-cv2BCp?cHS7Or@5~>eT;u-lw2QP5 z*((VVm1Lhe)roA`vhR&0+X!QeAt9Y?i7a6zS+0Z9>~$BCK;^#vAU(ikT2cFac=TbY9BdjRU5rweKk)en zZ2pAREXx$+4#gM=dSeO~B8pF;v%IV|S$+e(^ze@$qnFFlIh>y9d?jK>Ad_P|eRzDx zrbnRAPB-yi*-Y7|Wd0I4Y@51Rz3-S#4X3FRs1a3|v*qZ@_PV1A3Pns7uKuvqx|bHv zjA}=g8tU&y@c1^$uK0cE3aKf4Xp+tv|VK&MT|b2|wXvb{z?2=o-u*TM9GKT?R^z zwDdB(A2)xDRx7ya=x*A3Y33foO`S*?gqw?b&IJ*|Vbf%xBkl=p{XP6U+T&u_{1&G( zZe;$3xMAM`@{uIKSn4ip8!d8H`&B6@nEV1pim3NXSj7V^jyy%PNz&C)+rYJucrETq zW+R_T+*+<2+9i7Q%lUS~Oj8)OwJiX92PkYM;HS_tYorX-hpAhQX$$Ff$Zw8mFH~@W z@+!Vw{00wo?#fLvy)Pw7d+vi58a4yZ8-D)W=1Fw?21|CS_gGvpM>=p>zFdk9?Ly0W zbY+h$xZnS$q(eFJ6QedZNeHo1h@`E+pfyZoPB&~Oivapi8rF4oyDda{UZ5{|P5L7b zC?jgSST(8YWV(LPf1(U*3%^m)UuHG83Y`b3khH4`1)e@Grg$e0(}*ocW1Seu6fqxB zM3Gvgby{CRk7>>HM>mvrpUO=Cd|gqCm8yGlgR_zw?P}&!1lz(flc5p5?K{rGHK(2$ zYWr|xFVc2H(vO^TuaU^?xE6~@Q$Mtf5Ob7rIF*3%bG7{Ea>`m?%2y|Hn(Bp>tr%8( zYrP{p?ZqTJ-QzPFU8*vz*>1#uJ}i+^BY)ITOcrCxao2md8K(1T@m`)oWF<6 zPd6(;0$Uzg!2IlxC}5O>=5zE$~O{cvO1trXf= z7uAekryCvas{$KG*?gazkw+dRgYGX?rcX&HM0Xzkm0u!q$GQwEbxAjXB2ks%T)V}SX@@M6`Xek*lV0VJowHV%t1&B0ELJ(zYAjA{>8cIh&X$cO4@nmW&PoLeM6PE&rTv%s8o# z-)C)tK4SpmjWaSfQ46U;!nMD?gSnhnyK`0LbAOepRtlQ!^sD=6)7}HW>X6?fYy)tm zuk}t z^W!Te@S~d8rrN*P_q(7Thq26+N+ae;(7oi0H#${sA|H@u$v_<0TKxgk$P4IK8~!Rb zD!c=EvfWko*cCAa4^(lIG9w}r)(QAe7=_EOxm89mtJFJ32e?=lcK6-YI(jM^+=Zd; zZS*T2^F4#+ef^5m%49dwm6mJUS5oM)cW5G0X$$q{B8$24KYvvailGp{q!AdAF>$cF z8dg*l#c>-Uf;S|76~oiCWUMUzy}bX~(5JcE4Atc&a6gzaQ44k<$Mk$%5F^~dHjSrLJ--8~#JX~QeF9>z)Isq!UD;zf!wNNa zftP+SU0eOvc{r*aNulKb>@6*rKCM+gw0xPe66gB9>3kMnQ4?%;gzuF1ri1=VrRG5p3O!s)a z>5RaTBeK$L$bV8r17$*d^*Sm@^(@iGu-iMfGw}6@wFQ~2{n(_`45W@1emnA!Tme`Y zIEOE|@EW+QcvqT%tl%O?N;E>Nud*{kcYseG^(gYRUc4O880%ETcHPdc!_)%v4)J}T zlYH@#VKb^&?USH#H3exE-0k@t0qRi4%$QJ73D+I@KXV+(~Hr@72Q>@Jajq4o!Zh zMOD?9=P}hI?_=ZmKs4V_If7ZbSGZAs#;LcPdZC~U-lIY|N?CsX+d8J|uL_xS%~F~% zusj{HmFDbPXs(Fkb&*M9A3=0U!!4>IYnl1jgcxk(&Uhu2hMV|J%C341*>gLr7X>>1 z_#d^pHpNkjEfoc|$z!v{PF~nU2m%4zHke6$=@>qbN|C6Aa_p5G5X9oMAsC{DThKLw zW^j9HPsML-qVC#X*3Brsh!XSAvC47o_aM7?$4-MtF7k{eP)o*}Hxsl^Cb>Q;djwGX=e(h2FB4b9QWlR`U_PfZMs3hs|&}nW=WQo$E|sW4;WyT z@v14*KT>!EtoBvrt_9jON2$N=zeG!~=JtuzWOkK`ZI8@#4K!@%*cGbvS4YJoq|}?+ zxgMRP4mR2(D;DQ!)mh1@@#Vfv^uBKrF*{LG)#hPct0(irqawGr&?P$4VwPHcO2f6V zf{RzN>h;1DUx|gUIol_-PzW*A#{`pD93^Qo9v9oSQeKQbM=dvBV3_`Po#a}fSlX;8 zis8o#GwS8A_B{P_hOnTCM{#(*jMsI#y6Fo2&Nr+IU<&INd$%_IW(?KwA-7lx;2Dw$-esv2fqjOz8>`h=7Zhge0b zN+@+J<>Aya@BJ7EG0U3)`N*)=Cf68W-lUhkaDgS`!TL@bK7{^ki(=V~GT&ZNdC;JN z3WVRxu&Fv36$SWRWesu|Wo~_y?h~J?p&=heq{i~(sIK=)i!mo%e60rAop_5JBLh)~ z&ov_yf6DUsW@{AwEKHb)3vdHk%^TQji^GeBrVbLlgN>cowt!+Ts>>_?Dz*+>5E6qCuL8T#KJ&i%F_XBwkCm^Fj?s2r1q+Q7 z4@rJ}Urdt`O*>K-}Q+ z=D%EzEB4*lC}XGV_@E1O<_q?ds!@g%y3Op(_=V*o&;aR&nMmURBv9#2*(mwDLKhb3 z-{(yW>XIx1;`C(23j$(4RHsQ0D&&SY|8?_QqlF{gdne}(y{lI%+6vAs*s{S^diq!N zF%J6eCa(GHf&_Q#2AYo8ADQLHGsru-!3+A7L63v(oeeKZ@tI%4SHmB-)%^uvf4~6Z ze_!6iPG-svsPTK}+ZL*Ue`u~f%6#)!L)4butDL?Y9(l3xa70m@$Kpc#OCxe?KWBT6 zUINAHr0G&GpIB?ktq(F*77|>;L2mPw8$Y9bPU;bXvm$%RyhkqE<(2x(-~?%0B+pAb z_l3%m|AYq5N<_~s`1;yUL;w?&Ff+!$9vg|4f0`zNeZ8AgG@qu97<`Su=d8#y%) zhSx@Mmd8GZY)1XCO8FFXZ$%ap#!jltxQ;4{yp25iH}YxV7c@kz%t zzAEUdPlFqrFx~~N_>Gi(@3s=Yh8h<%U@4>aDzCfUW-)AMqg0zrTVoJqL-`S0GMl*- zT0GL}7Gp2HNI@g0_(m&uAhoH!{Kjtzp$7Mk&e+2tcK>;EokwQa=5St#!4WO|l*%}& zr56pY`6W4FsHf$bzf#p^aOB$2YI<)FVqe)CI?R5lTI=DE(cN1SIf2{D);`17q2MRJ>K15+*)tz!8gN{LbOj%$?@HT-s)%?h!;;Q5wvUns6l!DVHmwq&ek zee$dPLVo*9c{S75lbPT&lLeCXY`8j1s0MWk zI9_(Xx_M*eK|mL4<2J(O>66IgkeJGTydvd*IYjbYsTKS0O6=Zs~ zo$er}S<&O(gmjl`SIYn945+_eo}c2a1MEPvyO&Ke{W6C?{b<>% zZb+84TAr=dei8rUig4F$LWD4HYGT0%R{U1pLvyUHN>O?kVq1#zsh3f3nXn;e6S&|s zchgYWR$S2Fd|rKz0}7Pj{V2!)-TsL1-Y!!fIf8lvL>*ZJ7LRS2IQnmp#a{r#(*wsD zvEDmuM$K<-0(ZMrglhbLIq!U-1Akk%9mtm7*r89AJb7Ave^mCZ^i{jleSZNkADYMY)cE=fxJXT2Tb-krRm^=L z-QQ9#_2GRnWR7z(t*#mVaHimDiR^p>^ql5;Zq#}1v%R-SaAiiEA*S6W7T&uOF^O4w zoL0q7p<~m820(#g$xUVTvcq+B3~K~~IYZt#v;2+Au9c}vUYbxJNyANj#x7xrAAG!w z+Z@kS%^c7M`#WGOEat$`$Y!(>GjX}Cckdot$ z)}4M17F;c7REJO6WFMtrff)8K^WD5aTA)$(;sxskPv8=sYBld6UpCA(U>E$cyb zP1MED1?1}nV5ShfF4vYkN23YtWlMIuh7I*^Qy2;Xu>_c?>oh{N={J1&X@_SYRp9+X zS|b`up_A_Hm@AsaIYyqbJ+nujCm^_8m`k(Dmay*$$?^wB0;n8nT|84lA~? zp+`t-R}=4{HwC5~Mh#b!Vv|1bIKTIPEWE9y)W{R?SI1?CZzGRjjl-nchVw3uSE(9c zmV7o0x4M9}?zN?zGhXfcckun)MOhsaZ4B~`2mWHv5@yB;h%hG`8mrcW$#sGu$)(3l zxtO69--9UiI1=2&Vvzqw;R*%pv@&A-0rK=hUGd1uzpE9}Z5IbIaWSum#B*vSte@Yd zh>wjOiY2e46V9xNv==zQ~=aJvu&W4H6$R;*ALL=&$|)rZ62D~_c@!{aqLh^dTthOw>{ zi9S{%vs$};j3~YO7xyAy037le+SezsV95TBSv6Y}}Y&>0*fpO1P?3hIhHmp9-Ls0#9t=m-5D>x5%ycrOX=mP_KJb;>-Q= zVXKbTbGp!m5|f2|OBRqB*zHAWQPRElpR1V(nJ(=csL|KnZwbf;?`efWM-KcWeNN-} zU2dldyRvdY$^$qU?G+Z8%~abay4esB>qtzZhs9=w3!+o+2x%|-Ip5&c~Ceh&!jq7 z?CYUF-cxZeA6`ePYri!vNjGVe?DK%|`&t(;n8}$GOXZ#!-1?weU>W4-!}{ED8y>p< zv-A8rPRr~Ul_y(gqBRPPxX4fbneeUqjv3w`+ft~}-=x38Z!8VunH=n1jA#+ai>m#4 zF=9UK_-PL7tithNi5V-ar~Se?vcO=LCK;d|Q+|y=Q5=@NMg{xM+uQ59HovT2trisB z$faEl9Fr;VzogMSq!=lkW$2#@3OyLnaF5Vy4~cen!Lo*1FT20re+y@`(}m8Zm~X{fDh_)~o8~O+dNqCf z0)(`d=G3vvHk`?^-;Bm&S{9ZY_DC03hUxnR`aD9U8?&>I4i1(yHS51=AGS}~h4Q*B zd>Py6o^Mp9i8Tw^O8jT#JV+>r`E#Nbkhw~+1lP>gh z2cC8>sp2Oa&r%~6CTbi6lN^ph3rdCa#o{O`ZJn+iMggbZliMlCm+y^EWdo5w!7o#B zLDjKbhqVd^G&UovdUvqwZe-8p`!PkusHr9Vg>tu*d0&p|?Z{;4rp#Re*!-oRg@4^F zY@qZi^Pl?4_5I&>yx-k(`mnDLc{Jw$`>vFm)Lqguv8OJEXS81=ZfGwFeGxTbwXXMf zzL9Hp7D6rCFB(C&^Y=XPaVf$GoUUUSTS=UlEz5{g5!&JZ$j?ZTMTh(*R} zW{hW+?;gTo8J?~MpX{tf^Jg3-E7sV5GaHt**m|iQvt`H~VEv_E6(Ad0n~okWxKbm5 zp9TI-9_mCojPh$*MAGQQlr&+t20qEB;)pe%-aj<Q@?Rnx;Uy;BbPS0=E=XZ_ep%RIbs$lL|msZpapOSvEwVU7Gd4!ot&yYmU~hKXs%GlN0q_VJ zcJZT~P|l1ebf%I1?CGd?Wb5C}g%S!5_FBcy++0aV11M@P{S`P@pU~^KxWXI67g`WM z=jgzr+Y)b+uvTdLp5wYNP9R~fP6_XLz#!L53GG(VsN}#2{S?^kh`~w!=p>|pP)QaQ zPeopuuuJz+d0$$k(z|(c$;UB(W0cf@O*@2gPTYSi6gPm%s-}*u1qlg4)ZNMGjS>l8 zTBGTQaOf0}KM&RtZ%>WVDavux2V~{-Q!~dh#HT9i)}iIQr~?rKJmS1CvU-dY1l;CS zg8UsDC;N~bnp1P*o2G*|t0fHTOxo$y9tLA^Zx}Mrak%gXSdmkbQC0xb1&=%$1l$<;|lrTm3}5)h*_nTzjZZBK|X$)Sp4aMAN3!&O$@CndiD={D0h2` z{!`}Qr;Hqnp{71m@T$A$iO)CJ+>BEf&}Z2~b|OPY>626YRu$sB8S(vYA=`daW9+gV z>}rI{c)cqX-I$ozS-|9*bxgL#x&u86%^ z=eQC+ZiKbOF}`$Z@Uv`>ETK>yIOCuChJl*%ohm8GBTSuA=G~rI7nk2Y(URBW~(-}+w1oM z>$h@Lgw%OU7SdPvCiviXzvZ0N(AFo;s&e^e-8(1mM?F+s0HM<3?L$2mkm3r5KmR+tdLUr;#_ zT2635uuS4YaCJx;^X<^nK&PrNlaQI&YDcS20G-oB?wP_zTw|^3Jm_r101#TIFf<2jxDe}wb3#2|PESIR?=CI_5#7of^Mk!eLbV0eM#b?{>Fz{S$tOi;$V%up7!*6U_hfqY#i@ z>R=o}?wAOWZPj%^*5?gCV2(np!RLHI3u5h*SVsXAP@#ANDjd@^4^^# zg^t%{0qRvb17fhBh3m4ZK7oJk3jy~vG84`#$yC4h8bM?F?NgI~IMsu85DHgaSpnus zaOnXcMt<>nrfwyf%GyLo2>pzsM1)lWJ8;R}GtlzHXPdM8B4_zGc>Sl*o znZGq<4?SJ_Y{@ME^wtJ8c_tD?02&;0hKe}vsPg=Gex1(>^&)+VIIcbhWo?S~_%lpf zLI2h=e2E4CkmA{g715#=Ha6=jv%GoXQ09WZnpHCuJXrW)H48)@Zfod-9)N-T^IiY& z4@DD;ljwEYO9m2`bPq)v@o6Q+qLuyD*qhM~ehjHk#6QoK!h8pbB^O?fJ8M%ReTccQ zq5EOa767|33~F-y$Cw6n0EE8BXxAnUtkw$vE9W$I2w)V#RhTbT`UK2Dnp!N07e$)d zz~3EjQaiXFvUdBJ^rZ*(I;sCfG_?r?a&_Njnkda)l3oYhsaP%9tkdX(j5U_*afknz z1FS(d)i>e@-=!4hU0JA?L;o}gKzgbU{rMA?KG*+$jzkKyIn44EwYj$vG9I)M#iJg) z*7@GX9YUWLe)=LeVCR>eHxQpTO`(JLmK=c;mU$q~tLtDa{6SeZO_9-aG$yKA{eXFx zOmV<`sxUy=jqWpN@V-NgM-gvb0qqwP`zi zHS1A9!GkkT67}(W!;K$;8V|F);A4{T0!L-&%{X}D?;IN>y$l>z!1c~h{L#&N$58)w zTm%A26)N5sdlGqpyt6SA#|Q>kkun>@5=EP40g2_khP$Zc{NC4>*d~&7*>Rs@6 zkgJ}*&R^Sr^%8byPaI^EJ*ex#&<`#O7XhGfJP=}|WTfU!`$cU8HQ^ql_v{8E@9;%p z4U&kaAB3&%W`M%X)C78@4Gw+-64OinzVx9Ysp{1$-Kny145Wf-`+hP*CSqlp=4HyN z!67UpNOgm9RHs!E^5p2UURW=pceJQ!G?S|ZUwO7u5yA+?hv`G22aC3L8V)) zjZCz8bvxR4)F7c8!Yj&xenin#3F2m_qokDlvwu(;61B{U>a}rTXKC}tAuBe?FdI$hgpmJ@~e*Vfow79po z7ue{>w`)ektuv$e4SNZA-ZONZegyFd_keGH^mz5Toz+V6u4>EJLka1)aq5%71?oCu zXXfSpBO>b4ZfUr-#68`;j5(SYG^gN6VmaBuMp@}2zmLEmU?gFudn`Oe2Nmg)&>9v0 zXCY@g*(0szY9*)d+AD+TbLU&N7((M&VoBG5(D|p)%4?b%{d)=Q#8#_9s9n=>}N^J040#l)6P&j(j-|X^G3D?*c`LP^9))iDDA|=o^ZV zAM5+h_1KAR)>uC!sM{2NjAw2l0dJvpdYA+S_^@n1G}4NHx&1Gv#nFDQ*F17hu0Qzs zL?PR@+~9|_6@>Wyqy3I+vYo<136aTHaYgzJV~pu5{7}qEj?IdJOwJiWq@9wgKVl^jo*bJSJ4#=Az9iD`>7(`{OmrG_3U1G)>{sd>PEbZmG7< zTWo1uQZSe`m|L#%9LtM_`v7$Q^*ejXe&$Zy_$#ch+EIs`8`f>O>srBnyZdt~eAZz( zh@M2ksgQlJ$rWZ0Yx|b#7D$t)c_r@@IpfPjuXOM!#y`6#GH0xhaaQX{X^!p*Bv&2R zRcXZDn$Uj_D|KwY9hAP{FCG^vpi4PGO}`Dy`vDdkM}XpCv31sbc;oX|YldH=#Wz7A zMyq|xwmz%coANsv+U8kK`kH-u)t|#v%2bc=S!dUG zxWK{gbKww+UYZ;@zR$E)>Iv%Tl-AiG|9BdCQ;vA}mEEr5%*jP!&R28pj5yCrhQs42 zgBdrRJ$ELhV4!q&U)Itd%{4Jc)BJc-bNEXtSGBg}O3@W?w#uUe?WSON@}#GW=mal^ zXfF9y*Tx+n$nPi%24y<2*e%aVh*?~&n+dhG(&}#1JLLN^dxw}W6S+b5%HFuVfq5pv z`4f4H0e@zpMs=58s_pVrby3YY_b_?Gr*2;nLRmK>I{B;2{wD5`UYuoNaRJ6OmcBZJ z3nb~x&N}ae5pfND1giOIBS>gWojPrBLP)yL$vx5Y=$4t1JoO#IKf6NY`?MY=pc9#P z(ca);*#Oeawa|vuvvXau-B7K^-6q7rKd>vM*f}gopO|*z2d&WqVVY99usIhc0`Hs*wuxjtiIvzRz`J`MkbA*UDO34{w=+@CrGl_ynm% zpg`bqlDSYw^=yYQ26}AvhlrfGt0seaFj%@qhCy|oh#*+ypFja*w= zHyfl}ppOn^e!qTGvj}N@a)(t!Q=LGy&Euj(GLT8V8N1caFnmc@S+d?WVzQp7pj^S# znmMq`2*WKj5}rernk8vlVEY$_RQT*&k~>|D^2FqD=>fL}J3gom zgeyusmT}R^Y}A;h10ri2+s~Gjx9ooRqTiL~9XV=sP2}JlMCEzqdGV{+;@g2rwVD?c zmQ6=LXSo@O>D?KDR-myJ}#DonPtHI{hSYAPplq0c|>JZ3jFt>rE zuI@I$g3mTBS$L=h(YHNUyn3EboQjJVkmrCa{7yG!y$BmpqyELx7xLd!+dQ8yxoT)S zwS1;l(Ij8CBfD9NqQw&59K5tCF*8x^cTZ~IS-alL&mhKxaD<$@!@axxsc>KRi)`WO zXIu1C!@enjkqlF%!3RB4*CjLmIrzg88pWH*ENqRKz`p%plA>#Hg>w~FhlW=Z3LkLE z;DTVqK5$1lmE4jxKD|0d*S=*eB_nvfQb^?j5^RqCKE*slq4bJFeR69TEi2k53Q?T+ zWapYs<|^pu$?9ft62$t(Mk8C{HPQu+$j%Lg&+SmlQenB1Zt1tyq@jV2KPHmJiRD!s zy~g5?)v!e`!!PC-I!}yZr@!Vz_4^YYY?5f#a9FjG?n&&rZ;|LXrI3ROPT{3*Z#3{7 z3QJF;jZQxNi)B>xPZ!kTZ8)ERxn?RT@i_U@0YHzbOS|FRTJT1!LFGMjQ;}H2D;za~ zha^bw+4vcFKdJD%;YsRlquEJni>r!3}A@In8U9^bR~{C*|_0)s<Yx%J)27M%C@ zV4WIGFzCWl<{_g8%GCuvHc9Z*iR$n}Et9g~6>z>g>r14EP0*TkrtIa+r3glJs4_4} z&d?U=yJKxfIs8zwPTJmd_BdZ z8Xayb_9$Io6}`HEfI0}DC#agp9}$4?B*L@2Z03Xc zNkwo561>c=1;0mi?>PXVKufU00?Gw2;eK#_^FyLh*IZKM0cku`_sv*>eW78@SFxkh z<;QwUi>fS8tH$}~chZ&pg6-vv4%~PA9MM9Gx1QtjexcE_^s0gA@0y`O-%k{3;Jl5& ztPqOaX&gW4QT_6ZJ~93693XGS-H*}7-wP&XKeJ0be4HdGYchOpT!WXK^h1J}Ui^V? z#Xxj*EI8;q(PO=!q>;Z_bg?#o;^++m^rl9g8p9?3ap_Rgm<^YfwhXq-(b}MZ%~7mp zO^rXfN+gV!9Gl^HKYJt8sQIIp%EC&G-%7OdaD|L_^b*Jyd}RC6w6`f-O=gYcmAJ$$ zXXB1%qcK*{D z#GW#%h&%TbG1>HvCcUk{76n+C#%y}84v%Ww-TbwuBZE_^3lyhiA5k46jo-b%!ouac zUQg)-=*X`qz$yN-kFhXm5mu(^qQsvw7UP0*cYhp*_OV!4IV}}R2ntAyuXOv5{5tqI z+*wX(Dun>fRK&ve8n=pYZow-x2LnIWZFZXL=2M-5Yr!(T4bpKewx_X3njdHl-!ati1F^bqgn{|;rg{lA<1-#7W+ c-%r?h*`X(HkI$7eM}@|NNB/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==