diff --git a/fern/docs.yml b/fern/docs.yml index 490c6132..befe138e 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -172,6 +172,8 @@ navigation: - POST /v1/runs/{run_id}/cancel - POST /v1/runs/{run_id}/retry_webhook - POST /v1/run/tasks/login + - endpoint: POST /v1/run/tasks/download_files + hidden: true - GET /v1/runs/{run_id}/timeline - section: Workflows contents: diff --git a/pyproject.toml b/pyproject.toml index 935d1902..bec37630 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "skyvern" -version = "1.0.3" +version = "1.0.6" description = "" authors = [{ name = "Skyvern AI", email = "info@skyvern.com" }] requires-python = ">=3.11,<3.14" diff --git a/skyvern-ts/client/README.md b/skyvern-ts/client/README.md index f92dee52..7fad6695 100644 --- a/skyvern-ts/client/README.md +++ b/skyvern-ts/client/README.md @@ -12,6 +12,7 @@ The Skyvern TypeScript library provides convenient access to the Skyvern APIs fr - [Usage](#usage) - [Request and Response Types](#request-and-response-types) - [Exception Handling](#exception-handling) +- [File Uploads](#file-uploads) - [Advanced](#advanced) - [Additional Headers](#additional-headers) - [Additional Query String Parameters](#additional-query-string-parameters) @@ -56,7 +57,7 @@ following namespace: ```typescript import { Skyvern } from "@skyvern/client"; -const request: Skyvern.SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest = { +const request: Skyvern.RunTaskRequest = { ... }; ``` @@ -81,6 +82,50 @@ try { } ``` +## File Uploads + +You can upload files using the client: + +```typescript +import { createReadStream } from "fs"; +import { SkyvernClient } from "@skyvern/client"; +import * as fs from "fs"; + +const client = new SkyvernClient({ apiKey: "YOUR_API_KEY" }); +await client.uploadFile({ + file: fs.createReadStream("/path/to/your/file") +}); +``` +The client accepts a variety of types for file upload parameters: +* Stream types: `fs.ReadStream`, `stream.Readable`, and `ReadableStream` +* Buffered types: `Buffer`, `Blob`, `File`, `ArrayBuffer`, `ArrayBufferView`, and `Uint8Array` + +### Metadata + +You can configure metadata when uploading a file: +```typescript +const file: Uploadable.WithMetadata = { + data: createReadStream("path/to/file"), + filename: "my-file", // optional + contentType: "audio/mpeg", // optional + contentLength: 1949, // optional +}; +``` + +Alternatively, you can upload a file directly from a file path: +```typescript +const file : Uploadable.FromPath = { + path: "path/to/file", + filename: "my-file", // optional + contentType: "audio/mpeg", // optional + contentLength: 1949, // optional +}; +``` + +The metadata is used to set the `Content-Length`, `Content-Type`, and `Content-Disposition` headers. If not provided, the client will attempt to determine them automatically. +For example, `fs.ReadStream` has a `path` property which the SDK uses to retrieve the file size from the filesystem without loading it into memory. + + ## Advanced ### Additional Headers diff --git a/skyvern-ts/client/package-lock.json b/skyvern-ts/client/package-lock.json index 7cf36d76..e1950806 100644 --- a/skyvern-ts/client/package-lock.json +++ b/skyvern-ts/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@skyvern/client", - "version": "1.0.3", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@skyvern/client", - "version": "1.0.3", + "version": "1.0.6", "dependencies": { "playwright": "^1.48.0" }, @@ -28,7 +28,6 @@ "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.5.tgz", "integrity": "sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw==", "dev": true, - "license": "MIT OR Apache-2.0", "bin": { "biome": "bin/biome" }, @@ -58,7 +57,6 @@ "arm64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "darwin" @@ -75,7 +73,6 @@ "x64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "darwin" @@ -92,7 +89,6 @@ "arm64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" @@ -109,7 +105,6 @@ "arm64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" @@ -126,7 +121,6 @@ "x64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" @@ -143,7 +137,6 @@ "x64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" @@ -160,7 +153,6 @@ "arm64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "win32" @@ -177,7 +169,6 @@ "x64" ], "dev": true, - "license": "MIT OR Apache-2.0", "optional": true, "os": [ "win32" @@ -191,7 +182,6 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", "dev": true, - "license": "ISC", "dependencies": { "cookie": "^0.7.2" } @@ -201,7 +191,6 @@ "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", "dev": true, - "license": "ISC", "dependencies": { "statuses": "^2.0.1" } @@ -214,7 +203,6 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "aix" @@ -231,7 +219,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -248,7 +235,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -265,7 +251,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -282,7 +267,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -299,7 +283,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -316,7 +299,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -333,7 +315,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -350,7 +331,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -367,7 +347,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -384,7 +363,6 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -401,7 +379,6 @@ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -418,7 +395,6 @@ "mips64el" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -435,7 +411,6 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -452,7 +427,6 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -469,7 +443,6 @@ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -486,7 +459,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -503,7 +475,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "netbsd" @@ -520,7 +491,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "netbsd" @@ -537,7 +507,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openbsd" @@ -554,7 +523,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openbsd" @@ -571,7 +539,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openharmony" @@ -588,7 +555,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "sunos" @@ -605,7 +571,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -622,7 +587,6 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -639,7 +603,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -653,7 +616,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" } @@ -663,7 +625,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "dev": true, - "license": "MIT", "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" @@ -685,7 +646,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, - "license": "MIT", "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", @@ -713,7 +673,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" } @@ -723,7 +682,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -741,7 +699,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -752,7 +709,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -762,7 +718,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -772,15 +727,13 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -791,7 +744,6 @@ "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.8.tgz", "integrity": "sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==", "dev": true, - "license": "MIT", "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", @@ -808,15 +760,13 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@open-draft/logger": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", "dev": true, - "license": "MIT", "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" @@ -826,8 +776,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.53.5", @@ -837,7 +786,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -851,7 +799,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -865,7 +812,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -879,7 +825,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -893,7 +838,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -907,7 +851,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -921,7 +864,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -935,7 +877,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -949,7 +890,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -963,7 +903,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -977,7 +916,6 @@ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -991,7 +929,6 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1005,7 +942,6 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1019,7 +955,6 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1033,7 +968,6 @@ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1047,7 +981,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1061,7 +994,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1075,7 +1007,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openharmony" @@ -1089,7 +1020,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1103,7 +1033,6 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1117,7 +1046,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1131,7 +1059,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1142,7 +1069,6 @@ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, - "license": "MIT", "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" @@ -1152,22 +1078,19 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -1178,7 +1101,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, - "license": "MIT", "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -1188,22 +1110,19 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/node": { "version": "18.19.130", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -1212,15 +1131,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, - "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", @@ -1237,7 +1154,6 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, - "license": "MIT", "dependencies": { "tinyrainbow": "^2.0.0" }, @@ -1250,7 +1166,6 @@ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -1265,7 +1180,6 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -1280,7 +1194,6 @@ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, - "license": "MIT", "dependencies": { "tinyspy": "^4.0.3" }, @@ -1293,7 +1206,6 @@ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", @@ -1308,7 +1220,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -1318,29 +1229,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -1351,15 +1258,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1372,7 +1277,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, - "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -1382,7 +1286,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } @@ -1391,15 +1294,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1416,7 +1317,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -1430,7 +1330,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1443,7 +1342,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -1458,7 +1356,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -1468,22 +1365,19 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1496,7 +1390,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.13.0" }, @@ -1509,7 +1402,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1526,7 +1418,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -1544,7 +1435,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -1557,7 +1447,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -1567,7 +1456,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1583,17 +1471,15 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.8.tgz", - "integrity": "sha512-Y1fOuNDowLfgKOypdc9SPABfoWXuZHBOyCS4cD52IeZBhr4Md6CLLs6atcxVrzRmQ06E7hSlm5bHHApPKR/byA==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, - "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -1603,7 +1489,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1630,7 +1515,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1649,23 +1533,21 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001760", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", - "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "dev": true, "funding": [ { @@ -1680,15 +1562,13 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, - "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -1705,7 +1585,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1722,7 +1601,6 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 16" } @@ -1732,7 +1610,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.0" } @@ -1742,7 +1619,6 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, - "license": "ISC", "engines": { "node": ">= 12" } @@ -1752,7 +1628,6 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -1767,7 +1642,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -1785,7 +1659,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1797,22 +1670,19 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1822,7 +1692,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -1840,7 +1709,6 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -1849,22 +1717,19 @@ "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/enhanced-resolve": { "version": "5.18.4", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -1877,8 +1742,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/esbuild": { "version": "0.27.2", @@ -1886,7 +1750,6 @@ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -1927,7 +1790,6 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -1937,7 +1799,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -1951,7 +1812,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -1964,7 +1824,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -1974,7 +1833,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -1984,7 +1842,6 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -1994,7 +1851,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -2004,7 +1860,6 @@ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } @@ -2013,8 +1868,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-uri": { "version": "3.1.0", @@ -2030,15 +1884,13 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2051,7 +1903,6 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2065,7 +1916,6 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2074,22 +1924,19 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" + "dev": true }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/graphql": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -2099,7 +1946,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -2108,15 +1954,13 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -2125,15 +1969,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2143,7 +1985,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -2158,7 +1999,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -2173,29 +2013,25 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/loader-runner": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.11.5" }, @@ -2208,15 +2044,13 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } @@ -2225,15 +2059,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -2247,7 +2079,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2257,7 +2088,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2269,8 +2099,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/msw": { "version": "2.11.2", @@ -2278,7 +2107,6 @@ "integrity": "sha512-MI54hLCsrMwiflkcqlgYYNJJddY5/+S0SnONvhv1owOplvqohKSQyGejpNdUGyCwgs4IH7PqaNbPw/sKOEze9Q==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "@bundled-es-modules/cookie": "^2.0.1", "@bundled-es-modules/statuses": "^1.0.1", @@ -2323,7 +2151,6 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, - "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" } @@ -2339,7 +2166,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2351,43 +2177,37 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/outvariant": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/path-to-regexp": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 14.16" } @@ -2396,15 +2216,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2416,7 +2234,6 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "license": "Apache-2.0", "dependencies": { "playwright-core": "1.57.0" }, @@ -2434,7 +2251,6 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -2461,7 +2277,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2476,7 +2291,6 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -2486,7 +2300,6 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2496,7 +2309,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2505,15 +2317,13 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/rollup": { "version": "4.53.5", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -2568,15 +2378,13 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -2596,7 +2404,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -2609,7 +2416,6 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -2618,15 +2424,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC", "engines": { "node": ">=14" }, @@ -2639,7 +2443,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">= 12" } @@ -2649,7 +2452,6 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -2659,7 +2461,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -2670,7 +2471,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -2679,15 +2479,13 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2696,22 +2494,19 @@ "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/strict-event-emitter": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2726,7 +2521,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2739,7 +2533,6 @@ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, - "license": "MIT", "dependencies": { "js-tokens": "^9.0.1" }, @@ -2752,7 +2545,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -2765,7 +2557,6 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" }, @@ -2779,7 +2570,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -2798,7 +2588,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -2832,22 +2621,19 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, - "license": "MIT", "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" @@ -2864,7 +2650,6 @@ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12.0.0" }, @@ -2882,7 +2667,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -2895,7 +2679,6 @@ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, - "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -2905,7 +2688,6 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -2915,7 +2697,6 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -2925,7 +2706,6 @@ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", "dev": true, - "license": "MIT", "dependencies": { "tldts-core": "^7.0.19" }, @@ -2937,15 +2717,13 @@ "version": "7.0.19", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -2958,7 +2736,6 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "tldts": "^7.0.5" }, @@ -2971,7 +2748,6 @@ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", @@ -2992,7 +2768,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" }, @@ -3005,7 +2780,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3018,8 +2792,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/update-browserslist-db": { "version": "1.2.3", @@ -3040,7 +2813,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -3057,7 +2829,6 @@ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, - "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", @@ -3080,7 +2851,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -3092,7 +2862,6 @@ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12.0.0" }, @@ -3111,7 +2880,6 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -3125,7 +2893,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -3138,7 +2905,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, - "license": "MIT", "optional": true, "peer": true }, @@ -3147,7 +2913,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -3222,7 +2987,6 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, - "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -3295,7 +3059,6 @@ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", @@ -3322,7 +3085,6 @@ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12.0.0" }, @@ -3341,7 +3103,6 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -3355,7 +3116,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -3368,7 +3128,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -3443,7 +3202,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dev": true, - "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -3453,11 +3211,10 @@ } }, "node_modules/webpack": { - "version": "5.104.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.0.tgz", - "integrity": "sha512-5DeICTX8BVgNp6afSPYXAFjskIgWGlygQH58bcozPOXgo2r/6xx39Y1+cULZ3gTxUYQP88jmwLj2anu4Xaq84g==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, - "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -3506,7 +3263,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.13.0" } @@ -3515,15 +3271,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, - "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -3540,7 +3294,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -3555,7 +3308,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } @@ -3565,7 +3317,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -3584,7 +3335,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "license": "ISC", "engines": { "node": ">=12" } @@ -3594,7 +3344,6 @@ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, diff --git a/skyvern-ts/client/package.json b/skyvern-ts/client/package.json index e0b94a25..27f0dca5 100644 --- a/skyvern-ts/client/package.json +++ b/skyvern-ts/client/package.json @@ -1,6 +1,6 @@ { "name": "@skyvern/client", - "version": "1.0.3", + "version": "1.0.6", "private": false, "repository": { "type": "git", diff --git a/skyvern-ts/client/reference.md b/skyvern-ts/client/reference.md index 8129a6a9..133f9410 100644 --- a/skyvern-ts/client/reference.md +++ b/skyvern-ts/client/reference.md @@ -68,83 +68,6 @@ await client.runSdkAction({ ## -## Workflows -
client.workflows.setWorkflowTemplateStatus(workflowPermanentId, { ...params }) -> Record -
-
- -#### 📝 Description - -
-
- -
-
- -Set or unset a workflow as a template. - -Template status is stored at the workflow_permanent_id level (not per-version), -meaning all versions of a workflow share the same template status. -
-
-
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.workflows.setWorkflowTemplateStatus("workflow_permanent_id", { - is_template: true -}); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**workflowPermanentId:** `string` - -
-
- -
-
- -**request:** `Skyvern.SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest` - -
-
- -
-
- -**requestOptions:** `Workflows.RequestOptions` - -
-
-
-
- - -
-
-
- ## Scripts
client.scripts.runScript(scriptId) -> unknown
diff --git a/skyvern-ts/client/src/Client.ts b/skyvern-ts/client/src/Client.ts index e1cb84e0..83ab354d 100644 --- a/skyvern-ts/client/src/Client.ts +++ b/skyvern-ts/client/src/Client.ts @@ -2,7 +2,6 @@ import * as Skyvern from "./api/index.js"; import { Scripts } from "./api/resources/scripts/client/Client.js"; -import { Workflows } from "./api/resources/workflows/client/Client.js"; import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; import { mergeHeaders, mergeOnlyDefinedHeaders } from "./core/headers.js"; import * as core from "./core/index.js"; @@ -17,7 +16,6 @@ export declare namespace SkyvernClient { export class SkyvernClient { protected readonly _options: SkyvernClient.Options; - protected _workflows: Workflows | undefined; protected _scripts: Scripts | undefined; constructor(_options: SkyvernClient.Options = {}) { @@ -28,8 +26,8 @@ export class SkyvernClient { "x-api-key": _options?.apiKey, "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@skyvern/client", - "X-Fern-SDK-Version": "1.0.3", - "User-Agent": "@skyvern/client/1.0.3", + "X-Fern-SDK-Version": "1.0.6", + "User-Agent": "@skyvern/client/1.0.6", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -38,10 +36,6 @@ export class SkyvernClient { }; } - public get workflows(): Workflows { - return (this._workflows ??= new Workflows(this._options)); - } - public get scripts(): Scripts { return (this._scripts ??= new Scripts(this._options)); } @@ -948,6 +942,7 @@ export class SkyvernClient { * Retry sending the webhook for a run * * @param {string} runId - The id of the task run or the workflow run. + * @param {Skyvern.RetryRunWebhookRequest} request * @param {SkyvernClient.RequestOptions} requestOptions - Request-specific configuration. * * @throws {@link Skyvern.UnprocessableEntityError} @@ -957,13 +952,15 @@ export class SkyvernClient { */ public retryRunWebhook( runId: string, + request?: Skyvern.RetryRunWebhookRequest, requestOptions?: SkyvernClient.RequestOptions, ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__retryRunWebhook(runId, requestOptions)); + return core.HttpResponsePromise.fromPromise(this.__retryRunWebhook(runId, request, requestOptions)); } private async __retryRunWebhook( runId: string, + request?: Skyvern.RetryRunWebhookRequest, requestOptions?: SkyvernClient.RequestOptions, ): Promise> { const _headers: core.Fetcher.Args["headers"] = mergeHeaders( @@ -980,7 +977,10 @@ export class SkyvernClient { ), method: "POST", headers: _headers, + contentType: "application/json", queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request != null ? request : undefined, timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, abortSignal: requestOptions?.abortSignal, @@ -1102,6 +1102,91 @@ export class SkyvernClient { } } + /** + * @param {Skyvern.BodyUploadFileV1UploadFilePost} request + * @param {SkyvernClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link Skyvern.UnprocessableEntityError} + * + * @example + * import { createReadStream } from "fs"; + * await client.uploadFile({ + * file: fs.createReadStream("/path/to/your/file") + * }) + */ + public uploadFile( + request: Skyvern.BodyUploadFileV1UploadFilePost, + requestOptions?: SkyvernClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__uploadFile(request, requestOptions)); + } + + private async __uploadFile( + request: Skyvern.BodyUploadFileV1UploadFilePost, + requestOptions?: SkyvernClient.RequestOptions, + ): Promise> { + const _request = await core.newFormData(); + await _request.appendFile("file", request.file); + const _maybeEncodedRequest = await _request.getRequest(); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + this._options?.headers, + mergeOnlyDefinedHeaders({ + "x-api-key": requestOptions?.apiKey ?? this._options?.apiKey, + ..._maybeEncodedRequest.headers, + }), + 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)) ?? + environments.SkyvernEnvironment.Cloud, + "v1/upload_file", + ), + method: "POST", + headers: _headers, + queryParameters: requestOptions?.queryParams, + requestType: "file", + duplex: _maybeEncodedRequest.duplex, + body: _maybeEncodedRequest.body, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { data: _response.body as Skyvern.UploadFileResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new Skyvern.UnprocessableEntityError(_response.error.body as unknown, _response.rawResponse); + default: + throw new errors.SkyvernError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.SkyvernError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + rawResponse: _response.rawResponse, + }); + case "timeout": + throw new errors.SkyvernTimeoutError("Timeout exceeded when calling POST /v1/upload_file."); + case "unknown": + throw new errors.SkyvernError({ + message: _response.error.errorMessage, + rawResponse: _response.rawResponse, + }); + } + } + /** * Get all browser profiles for the organization * diff --git a/skyvern-ts/client/src/api/client/requests/BodyUploadFileV1UploadFilePost.ts b/skyvern-ts/client/src/api/client/requests/BodyUploadFileV1UploadFilePost.ts new file mode 100644 index 00000000..8264af6c --- /dev/null +++ b/skyvern-ts/client/src/api/client/requests/BodyUploadFileV1UploadFilePost.ts @@ -0,0 +1,13 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as core from "../../../core/index.js"; + +/** + * @example + * { + * file: fs.createReadStream("/path/to/your/file") + * } + */ +export interface BodyUploadFileV1UploadFilePost { + file: core.file.Uploadable; +} diff --git a/skyvern-ts/client/src/api/client/requests/index.ts b/skyvern-ts/client/src/api/client/requests/index.ts index 0e1cb35d..5215a6e3 100644 --- a/skyvern-ts/client/src/api/client/requests/index.ts +++ b/skyvern-ts/client/src/api/client/requests/index.ts @@ -1,3 +1,4 @@ +export type { BodyUploadFileV1UploadFilePost } from "./BodyUploadFileV1UploadFilePost.js"; export type { CreateBrowserProfileRequest } from "./CreateBrowserProfileRequest.js"; export type { CreateBrowserSessionRequest } from "./CreateBrowserSessionRequest.js"; export type { CreateCredentialRequest } from "./CreateCredentialRequest.js"; diff --git a/skyvern-ts/client/src/api/resources/index.ts b/skyvern-ts/client/src/api/resources/index.ts index 9852fa96..7c69f0c4 100644 --- a/skyvern-ts/client/src/api/resources/index.ts +++ b/skyvern-ts/client/src/api/resources/index.ts @@ -1,3 +1 @@ export * as scripts from "./scripts/index.js"; -export * from "./workflows/client/requests/index.js"; -export * as workflows from "./workflows/index.js"; diff --git a/skyvern-ts/client/src/api/resources/workflows/client/Client.ts b/skyvern-ts/client/src/api/resources/workflows/client/Client.ts deleted file mode 100644 index 7b14e6da..00000000 --- a/skyvern-ts/client/src/api/resources/workflows/client/Client.ts +++ /dev/null @@ -1,112 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type { BaseClientOptions, BaseRequestOptions } from "../../../../BaseClient.js"; -import { mergeHeaders, mergeOnlyDefinedHeaders } from "../../../../core/headers.js"; -import * as core from "../../../../core/index.js"; -import * as environments from "../../../../environments.js"; -import * as errors from "../../../../errors/index.js"; -import * as Skyvern from "../../../index.js"; - -export declare namespace Workflows { - export interface Options extends BaseClientOptions {} - - export interface RequestOptions extends BaseRequestOptions {} -} - -export class Workflows { - protected readonly _options: Workflows.Options; - - constructor(_options: Workflows.Options = {}) { - this._options = _options; - } - - /** - * Set or unset a workflow as a template. - * - * Template status is stored at the workflow_permanent_id level (not per-version), - * meaning all versions of a workflow share the same template status. - * - * @param {string} workflowPermanentId - * @param {Skyvern.SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest} request - * @param {Workflows.RequestOptions} requestOptions - Request-specific configuration. - * - * @throws {@link Skyvern.UnprocessableEntityError} - * - * @example - * await client.workflows.setWorkflowTemplateStatus("workflow_permanent_id", { - * is_template: true - * }) - */ - public setWorkflowTemplateStatus( - workflowPermanentId: string, - request: Skyvern.SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest, - requestOptions?: Workflows.RequestOptions, - ): core.HttpResponsePromise> { - return core.HttpResponsePromise.fromPromise( - this.__setWorkflowTemplateStatus(workflowPermanentId, request, requestOptions), - ); - } - - private async __setWorkflowTemplateStatus( - workflowPermanentId: string, - request: Skyvern.SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest, - requestOptions?: Workflows.RequestOptions, - ): Promise>> { - const { is_template: isTemplate } = request; - const _queryParams: Record = {}; - _queryParams.is_template = isTemplate.toString(); - const _headers: core.Fetcher.Args["headers"] = mergeHeaders( - this._options?.headers, - mergeOnlyDefinedHeaders({ "x-api-key": requestOptions?.apiKey ?? this._options?.apiKey }), - 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)) ?? - environments.SkyvernEnvironment.Cloud, - `v1/workflows/${core.url.encodePathParam(workflowPermanentId)}/template`, - ), - method: "PUT", - headers: _headers, - queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, - timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000, - maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, - abortSignal: requestOptions?.abortSignal, - }); - if (_response.ok) { - return { data: _response.body as Record, rawResponse: _response.rawResponse }; - } - - if (_response.error.reason === "status-code") { - switch (_response.error.statusCode) { - case 422: - throw new Skyvern.UnprocessableEntityError(_response.error.body as unknown, _response.rawResponse); - default: - throw new errors.SkyvernError({ - statusCode: _response.error.statusCode, - body: _response.error.body, - rawResponse: _response.rawResponse, - }); - } - } - - switch (_response.error.reason) { - case "non-json": - throw new errors.SkyvernError({ - statusCode: _response.error.statusCode, - body: _response.error.rawBody, - rawResponse: _response.rawResponse, - }); - case "timeout": - throw new errors.SkyvernTimeoutError( - "Timeout exceeded when calling PUT /v1/workflows/{workflow_permanent_id}/template.", - ); - case "unknown": - throw new errors.SkyvernError({ - message: _response.error.errorMessage, - rawResponse: _response.rawResponse, - }); - } - } -} diff --git a/skyvern-ts/client/src/api/resources/workflows/client/index.ts b/skyvern-ts/client/src/api/resources/workflows/client/index.ts deleted file mode 100644 index 195f9aa8..00000000 --- a/skyvern-ts/client/src/api/resources/workflows/client/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./requests/index.js"; diff --git a/skyvern-ts/client/src/api/resources/workflows/client/requests/SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest.ts b/skyvern-ts/client/src/api/resources/workflows/client/requests/SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest.ts deleted file mode 100644 index 768d68a3..00000000 --- a/skyvern-ts/client/src/api/resources/workflows/client/requests/SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest.ts +++ /dev/null @@ -1,11 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** - * @example - * { - * is_template: true - * } - */ -export interface SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest { - is_template: boolean; -} diff --git a/skyvern-ts/client/src/api/resources/workflows/client/requests/index.ts b/skyvern-ts/client/src/api/resources/workflows/client/requests/index.ts deleted file mode 100644 index c5fa001f..00000000 --- a/skyvern-ts/client/src/api/resources/workflows/client/requests/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest } from "./SetWorkflowTemplateStatusV1WorkflowsWorkflowPermanentIdTemplatePutRequest.js"; diff --git a/skyvern-ts/client/src/api/resources/workflows/index.ts b/skyvern-ts/client/src/api/resources/workflows/index.ts deleted file mode 100644 index 914b8c3c..00000000 --- a/skyvern-ts/client/src/api/resources/workflows/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./client/index.js"; diff --git a/skyvern-ts/client/src/api/types/PromptAction.ts b/skyvern-ts/client/src/api/types/PromptAction.ts index 4fe303ed..934fc9de 100644 --- a/skyvern-ts/client/src/api/types/PromptAction.ts +++ b/skyvern-ts/client/src/api/types/PromptAction.ts @@ -7,7 +7,7 @@ export interface PromptAction { /** The prompt to send to the LLM */ prompt: string; /** Optional JSON schema to structure the response */ - schema?: Record; + response_schema?: Record; /** Optional model configuration */ model?: Record; } diff --git a/skyvern-ts/client/src/api/types/RetryRunWebhookRequest.ts b/skyvern-ts/client/src/api/types/RetryRunWebhookRequest.ts new file mode 100644 index 00000000..7dc9a0da --- /dev/null +++ b/skyvern-ts/client/src/api/types/RetryRunWebhookRequest.ts @@ -0,0 +1,6 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface RetryRunWebhookRequest { + /** Optional webhook URL to send the payload to instead of the stored configuration */ + webhook_url?: string; +} diff --git a/skyvern-ts/client/src/api/types/UploadFileResponse.ts b/skyvern-ts/client/src/api/types/UploadFileResponse.ts new file mode 100644 index 00000000..ec36687c --- /dev/null +++ b/skyvern-ts/client/src/api/types/UploadFileResponse.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface UploadFileResponse { + /** S3 URI where the file was uploaded */ + s3_uri: string; + /** Presigned URL to access the uploaded file */ + presigned_url: string; +} diff --git a/skyvern-ts/client/src/api/types/index.ts b/skyvern-ts/client/src/api/types/index.ts index 45bf40eb..dad9059b 100644 --- a/skyvern-ts/client/src/api/types/index.ts +++ b/skyvern-ts/client/src/api/types/index.ts @@ -96,6 +96,7 @@ export * from "./PdfParserBlockYaml.js"; export * from "./PromptAction.js"; export * from "./PromptBranchCriteria.js"; export * from "./ProxyLocation.js"; +export * from "./RetryRunWebhookRequest.js"; export * from "./RunEngine.js"; export * from "./RunSdkActionRequestAction.js"; export * from "./RunSdkActionResponse.js"; @@ -127,6 +128,7 @@ export * from "./ThoughtType.js"; export * from "./TotpCode.js"; export * from "./TotpType.js"; export * from "./UploadFileAction.js"; +export * from "./UploadFileResponse.js"; export * from "./UploadToS3Block.js"; export * from "./UploadToS3BlockYaml.js"; export * from "./UrlBlock.js"; diff --git a/skyvern-ts/client/src/core/exports.ts b/skyvern-ts/client/src/core/exports.ts new file mode 100644 index 00000000..e415a8f6 --- /dev/null +++ b/skyvern-ts/client/src/core/exports.ts @@ -0,0 +1 @@ +export * from "./file/exports.js"; diff --git a/skyvern-ts/client/src/core/file/exports.ts b/skyvern-ts/client/src/core/file/exports.ts new file mode 100644 index 00000000..3b0b3967 --- /dev/null +++ b/skyvern-ts/client/src/core/file/exports.ts @@ -0,0 +1 @@ +export type { Uploadable } from "./types.js"; diff --git a/skyvern-ts/client/src/core/file/file.ts b/skyvern-ts/client/src/core/file/file.ts new file mode 100644 index 00000000..0bacc484 --- /dev/null +++ b/skyvern-ts/client/src/core/file/file.ts @@ -0,0 +1,217 @@ +import type { Uploadable } from "./types.js"; + +export async function toBinaryUploadRequest( + file: Uploadable, +): Promise<{ body: Uploadable.FileLike; headers?: Record }> { + const { data, filename, contentLength, contentType } = await getFileWithMetadata(file); + const request = { + body: data, + headers: {} as Record, + }; + if (filename) { + request.headers["Content-Disposition"] = `attachment; filename="${filename}"`; + } + if (contentType) { + request.headers["Content-Type"] = contentType; + } + if (contentLength != null) { + request.headers["Content-Length"] = contentLength.toString(); + } + return request; +} + +export async function toMultipartDataPart( + file: Uploadable, +): Promise<{ data: Uploadable.FileLike; filename?: string; contentType?: string }> { + const { data, filename, contentType } = await getFileWithMetadata(file, { + noSniffFileSize: true, + }); + return { + data, + filename, + contentType, + }; +} + +async function getFileWithMetadata( + file: Uploadable, + { noSniffFileSize }: { noSniffFileSize?: boolean } = {}, +): Promise { + if (isFileLike(file)) { + return getFileWithMetadata( + { + data: file, + }, + { noSniffFileSize }, + ); + } + + if ("path" in file) { + const fs = await import("fs"); + if (!fs || !fs.createReadStream) { + throw new Error("File path uploads are not supported in this environment."); + } + const data = fs.createReadStream(file.path); + const contentLength = + file.contentLength ?? (noSniffFileSize === true ? undefined : await tryGetFileSizeFromPath(file.path)); + const filename = file.filename ?? getNameFromPath(file.path); + return { + data, + filename, + contentType: file.contentType, + contentLength, + }; + } + if ("data" in file) { + const data = file.data; + const contentLength = + file.contentLength ?? + (await tryGetContentLengthFromFileLike(data, { + noSniffFileSize, + })); + const filename = file.filename ?? tryGetNameFromFileLike(data); + return { + data, + filename, + contentType: file.contentType ?? tryGetContentTypeFromFileLike(data), + contentLength, + }; + } + + throw new Error(`Invalid FileUpload of type ${typeof file}: ${JSON.stringify(file)}`); +} + +function isFileLike(value: unknown): value is Uploadable.FileLike { + return ( + isBuffer(value) || + isArrayBufferView(value) || + isArrayBuffer(value) || + isUint8Array(value) || + isBlob(value) || + isFile(value) || + isStreamLike(value) || + isReadableStream(value) + ); +} + +async function tryGetFileSizeFromPath(path: string): Promise { + try { + const fs = await import("fs"); + if (!fs || !fs.promises || !fs.promises.stat) { + return undefined; + } + const fileStat = await fs.promises.stat(path); + return fileStat.size; + } catch (_fallbackError) { + return undefined; + } +} + +function tryGetNameFromFileLike(data: Uploadable.FileLike): string | undefined { + if (isNamedValue(data)) { + return data.name; + } + if (isPathedValue(data)) { + return getNameFromPath(data.path.toString()); + } + return undefined; +} + +async function tryGetContentLengthFromFileLike( + data: Uploadable.FileLike, + { noSniffFileSize }: { noSniffFileSize?: boolean } = {}, +): Promise { + if (isBuffer(data)) { + return data.length; + } + if (isArrayBufferView(data)) { + return data.byteLength; + } + if (isArrayBuffer(data)) { + return data.byteLength; + } + if (isBlob(data)) { + return data.size; + } + if (isFile(data)) { + return data.size; + } + if (noSniffFileSize === true) { + return undefined; + } + if (isPathedValue(data)) { + return await tryGetFileSizeFromPath(data.path.toString()); + } + return undefined; +} + +function tryGetContentTypeFromFileLike(data: Uploadable.FileLike): string | undefined { + if (isBlob(data)) { + return data.type; + } + if (isFile(data)) { + return data.type; + } + + return undefined; +} + +function getNameFromPath(path: string): string | undefined { + const lastForwardSlash = path.lastIndexOf("/"); + const lastBackSlash = path.lastIndexOf("\\"); + const lastSlashIndex = Math.max(lastForwardSlash, lastBackSlash); + return lastSlashIndex >= 0 ? path.substring(lastSlashIndex + 1) : path; +} + +type NamedValue = { + name: string; +} & unknown; + +type PathedValue = { + path: string | { toString(): string }; +} & unknown; + +type StreamLike = { + read?: () => unknown; + pipe?: (dest: unknown) => unknown; +} & unknown; + +function isNamedValue(value: unknown): value is NamedValue { + return typeof value === "object" && value != null && "name" in value; +} + +function isPathedValue(value: unknown): value is PathedValue { + return typeof value === "object" && value != null && "path" in value; +} + +function isStreamLike(value: unknown): value is StreamLike { + return typeof value === "object" && value != null && ("read" in value || "pipe" in value); +} + +function isReadableStream(value: unknown): value is ReadableStream { + return typeof value === "object" && value != null && "getReader" in value; +} + +function isBuffer(value: unknown): value is Buffer { + return typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(value); +} + +function isArrayBufferView(value: unknown): value is ArrayBufferView { + return typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(value); +} + +function isArrayBuffer(value: unknown): value is ArrayBuffer { + return typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer; +} + +function isUint8Array(value: unknown): value is Uint8Array { + return typeof Uint8Array !== "undefined" && value instanceof Uint8Array; +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== "undefined" && value instanceof Blob; +} + +function isFile(value: unknown): value is File { + return typeof File !== "undefined" && value instanceof File; +} diff --git a/skyvern-ts/client/src/core/file/index.ts b/skyvern-ts/client/src/core/file/index.ts new file mode 100644 index 00000000..fc16dd52 --- /dev/null +++ b/skyvern-ts/client/src/core/file/index.ts @@ -0,0 +1,2 @@ +export * from "./file.js"; +export * from "./types.js"; diff --git a/skyvern-ts/client/src/core/file/types.ts b/skyvern-ts/client/src/core/file/types.ts new file mode 100644 index 00000000..531b6927 --- /dev/null +++ b/skyvern-ts/client/src/core/file/types.ts @@ -0,0 +1,81 @@ +/** + * A file that can be uploaded. Can be a file-like object (stream, buffer, blob, etc.), + * a path to a file, or an object with a file-like object and metadata. + */ +export type Uploadable = Uploadable.FileLike | Uploadable.FromPath | Uploadable.WithMetadata; + +export namespace Uploadable { + /** + * Various file-like objects that can be used to upload a file. + */ + export type FileLike = + | ArrayBuffer + | ArrayBufferLike + | ArrayBufferView + | Uint8Array + | import("buffer").Buffer + | import("buffer").Blob + | import("buffer").File + | import("stream").Readable + | import("stream/web").ReadableStream + | globalThis.Blob + | globalThis.File + | ReadableStream; + + /** + * A file path with optional metadata, used for uploading a file from the file system. + */ + export type FromPath = { + /** The path to the file to upload */ + path: string; + /** + * Optional override for the file name (defaults to basename of path). + * This is used to set the `Content-Disposition` header in upload requests. + */ + filename?: string; + /** + * Optional MIME type of the file (e.g., 'image/jpeg', 'text/plain'). + * This is used to set the `Content-Type` header in upload requests. + */ + contentType?: string; + /** + * Optional file size in bytes. + * If not provided, the file size will be determined from the file system. + * The content length is used to set the `Content-Length` header in upload requests. + */ + contentLength?: number; + }; + + /** + * A file-like object with metadata, used for uploading files. + */ + export type WithMetadata = { + /** The file data */ + data: FileLike; + /** + * Optional override for the file name (defaults to basename of path). + * This is used to set the `Content-Disposition` header in upload requests. + */ + filename?: string; + /** + * Optional MIME type of the file (e.g., 'image/jpeg', 'text/plain'). + * This is used to set the `Content-Type` header in upload requests. + * + * If not provided, the content type may be determined from the data itself. + * * If the data is a `File`, `Blob`, or similar, the content type will be determined from the file itself, if the type is set. + * * Any other data type will not have a content type set, and the upload request will use `Content-Type: application/octet-stream` instead. + */ + contentType?: string; + /** + * Optional file size in bytes. + * The content length is used to set the `Content-Length` header in upload requests. + * If the content length is not provided and cannot be determined, the upload request will not include the `Content-Length` header, but will use `Transfer-Encoding: chunked` instead. + * + * If not provided, the file size will be determined depending on the data type. + * * If the data is of type `fs.ReadStream` (`createReadStream`), the size will be determined from the file system. + * * If the data is a `Buffer`, `ArrayBuffer`, `Uint8Array`, `Blob`, `File`, or similar, the size will be determined from the data itself. + * * If the data is a `Readable` or `ReadableStream`, the size will not be determined. + */ + contentLength?: number; + }; +} diff --git a/skyvern-ts/client/src/core/form-data-utils/FormDataWrapper.ts b/skyvern-ts/client/src/core/form-data-utils/FormDataWrapper.ts new file mode 100644 index 00000000..bea0cf82 --- /dev/null +++ b/skyvern-ts/client/src/core/form-data-utils/FormDataWrapper.ts @@ -0,0 +1,140 @@ +import { toMultipartDataPart, type Uploadable } from "../../core/file/index.js"; +import { toJson } from "../../core/json.js"; +import { RUNTIME } from "../runtime/index.js"; + +interface FormDataRequest { + body: Body; + headers: Record; + duplex?: "half"; +} + +export async function newFormData(): Promise { + return new FormDataWrapper(); +} + +export class FormDataWrapper { + private fd: FormData = new FormData(); + + public async setup(): Promise { + // noop + } + + public append(key: string, value: unknown): void { + this.fd.append(key, String(value)); + } + + public async appendFile(key: string, value: Uploadable): Promise { + const { data, filename, contentType } = await toMultipartDataPart(value); + const blob = await convertToBlob(data, contentType); + if (filename) { + this.fd.append(key, blob, filename); + } else { + this.fd.append(key, blob); + } + } + + public getRequest(): FormDataRequest { + return { + body: this.fd, + headers: {}, + duplex: "half" as const, + }; + } +} + +type StreamLike = { + read?: () => unknown; + pipe?: (dest: unknown) => unknown; +} & unknown; + +function isStreamLike(value: unknown): value is StreamLike { + return typeof value === "object" && value != null && ("read" in value || "pipe" in value); +} + +function isReadableStream(value: unknown): value is ReadableStream { + return typeof value === "object" && value != null && "getReader" in value; +} + +function isBuffer(value: unknown): value is Buffer { + return typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(value); +} + +function isArrayBufferView(value: unknown): value is ArrayBufferView { + return ArrayBuffer.isView(value); +} + +async function streamToBuffer(stream: unknown): Promise { + if (RUNTIME.type === "node") { + const { Readable } = await import("stream"); + + if (stream instanceof Readable) { + const chunks: Buffer[] = []; + for await (const chunk of stream) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + return Buffer.concat(chunks); + } + } + + if (isReadableStream(stream)) { + const reader = stream.getReader(); + const chunks: Uint8Array[] = []; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + } + } finally { + reader.releaseLock(); + } + + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + + return Buffer.from(result); + } + + throw new Error( + `Unsupported stream type: ${typeof stream}. Expected Node.js Readable stream or Web ReadableStream.`, + ); +} + +async function convertToBlob(value: unknown, contentType?: string): Promise { + if (isStreamLike(value) || isReadableStream(value)) { + const buffer = await streamToBuffer(value); + return new Blob([buffer], { type: contentType }); + } + + if (value instanceof Blob) { + return value; + } + + if (isBuffer(value)) { + return new Blob([value], { type: contentType }); + } + + if (value instanceof ArrayBuffer) { + return new Blob([value], { type: contentType }); + } + + if (isArrayBufferView(value)) { + return new Blob([value], { type: contentType }); + } + + if (typeof value === "string") { + return new Blob([value], { type: contentType }); + } + + if (typeof value === "object" && value !== null) { + return new Blob([toJson(value)], { type: contentType ?? "application/json" }); + } + + return new Blob([String(value)], { type: contentType }); +} diff --git a/skyvern-ts/client/src/core/form-data-utils/encodeAsFormParameter.ts b/skyvern-ts/client/src/core/form-data-utils/encodeAsFormParameter.ts new file mode 100644 index 00000000..cfc67413 --- /dev/null +++ b/skyvern-ts/client/src/core/form-data-utils/encodeAsFormParameter.ts @@ -0,0 +1,12 @@ +import { toQueryString } from "../url/qs.js"; + +export function encodeAsFormParameter(value: unknown): Record { + const stringified = toQueryString(value, { encode: false }); + + const keyValuePairs = stringified.split("&").map((pair) => { + const [key, value] = pair.split("="); + return [key, value] as const; + }); + + return Object.fromEntries(keyValuePairs); +} diff --git a/skyvern-ts/client/src/core/form-data-utils/index.ts b/skyvern-ts/client/src/core/form-data-utils/index.ts new file mode 100644 index 00000000..1188f80c --- /dev/null +++ b/skyvern-ts/client/src/core/form-data-utils/index.ts @@ -0,0 +1,2 @@ +export { encodeAsFormParameter } from "./encodeAsFormParameter.js"; +export * from "./FormDataWrapper.js"; diff --git a/skyvern-ts/client/src/core/index.ts b/skyvern-ts/client/src/core/index.ts index bbb640d1..ad2bf501 100644 --- a/skyvern-ts/client/src/core/index.ts +++ b/skyvern-ts/client/src/core/index.ts @@ -1,3 +1,5 @@ export * from "./fetcher/index.js"; +export * as file from "./file/index.js"; +export * from "./form-data-utils/index.js"; export * from "./runtime/index.js"; export * as url from "./url/index.js"; diff --git a/skyvern-ts/client/src/exports.ts b/skyvern-ts/client/src/exports.ts new file mode 100644 index 00000000..7b70ee14 --- /dev/null +++ b/skyvern-ts/client/src/exports.ts @@ -0,0 +1 @@ +export * from "./core/exports.js"; diff --git a/skyvern-ts/client/src/index.ts b/skyvern-ts/client/src/index.ts index a923e77d..0910dfd3 100644 --- a/skyvern-ts/client/src/index.ts +++ b/skyvern-ts/client/src/index.ts @@ -3,5 +3,6 @@ export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; export { SkyvernClient } from "./Client.js"; export { SkyvernEnvironment } from "./environments.js"; export { SkyvernError, SkyvernTimeoutError } from "./errors/index.js"; +export * from "./exports.js"; export { Skyvern, SkyvernBrowser, SkyvernBrowserPageAgent, SkyvernBrowserPageAi } from "./library/index.js"; export type { SkyvernOptions, SkyvernBrowserPage } from "./library/index.js"; diff --git a/skyvern-ts/client/src/version.ts b/skyvern-ts/client/src/version.ts index 9add6892..b09139cf 100644 --- a/skyvern-ts/client/src/version.ts +++ b/skyvern-ts/client/src/version.ts @@ -1 +1 @@ -export const SDK_VERSION = "1.0.3"; +export const SDK_VERSION = "1.0.6"; diff --git a/skyvern-ts/client/tests/unit/file/file.test.ts b/skyvern-ts/client/tests/unit/file/file.test.ts new file mode 100644 index 00000000..d7c4570b --- /dev/null +++ b/skyvern-ts/client/tests/unit/file/file.test.ts @@ -0,0 +1,498 @@ +import fs from "fs"; +import { join } from "path"; +import { Readable } from "stream"; +import { toBinaryUploadRequest, type Uploadable } from "../../../src/core/file/index"; + +describe("toBinaryUploadRequest", () => { + const TEST_FILE_PATH = join(__dirname, "..", "test-file.txt"); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("Buffer input", () => { + it("should handle Buffer with all metadata", async () => { + const buffer = Buffer.from("test data"); + const input: Uploadable.WithMetadata = { + data: buffer, + filename: "test.txt", + contentType: "text/plain", + contentLength: 42, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(buffer); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="test.txt"', + "Content-Type": "text/plain", + "Content-Length": "42", + }); + }); + + it("should handle Buffer without metadata", async () => { + const buffer = Buffer.from("test data"); + const input: Uploadable.WithMetadata = { + data: buffer, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(buffer); + expect(result.headers).toEqual({ + "Content-Length": "9", // buffer.length + }); + }); + + it("should handle Buffer passed directly", async () => { + const buffer = Buffer.from("test data"); + + const result = await toBinaryUploadRequest(buffer); + + expect(result.body).toBe(buffer); + expect(result.headers).toEqual({ + "Content-Length": "9", // buffer.length + }); + }); + }); + + describe("ArrayBuffer input", () => { + it("should handle ArrayBuffer with metadata", async () => { + const arrayBuffer = new ArrayBuffer(10); + const input: Uploadable.WithMetadata = { + data: arrayBuffer, + filename: "data.bin", + contentType: "application/octet-stream", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(arrayBuffer); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="data.bin"', + "Content-Type": "application/octet-stream", + "Content-Length": "10", // arrayBuffer.byteLength + }); + }); + + it("should handle ArrayBuffer passed directly", async () => { + const arrayBuffer = new ArrayBuffer(10); + + const result = await toBinaryUploadRequest(arrayBuffer); + + expect(result.body).toBe(arrayBuffer); + expect(result.headers).toEqual({ + "Content-Length": "10", // arrayBuffer.byteLength + }); + }); + }); + + describe("Uint8Array input", () => { + it("should handle Uint8Array with metadata", async () => { + const uint8Array = new Uint8Array([1, 2, 3, 4, 5]); + const input: Uploadable.WithMetadata = { + data: uint8Array, + filename: "bytes.bin", + contentType: "application/octet-stream", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(uint8Array); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="bytes.bin"', + "Content-Type": "application/octet-stream", + "Content-Length": "5", // uint8Array.byteLength + }); + }); + + it("should handle Uint8Array passed directly", async () => { + const uint8Array = new Uint8Array([1, 2, 3, 4, 5]); + + const result = await toBinaryUploadRequest(uint8Array); + + expect(result.body).toBe(uint8Array); + expect(result.headers).toEqual({ + "Content-Length": "5", // uint8Array.byteLength + }); + }); + }); + + describe("Blob input", () => { + it("should handle Blob with metadata", async () => { + const blob = new Blob(["test content"], { type: "text/plain" }); + const input: Uploadable.WithMetadata = { + data: blob, + filename: "override.txt", + contentType: "text/html", // Override blob's type + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(blob); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="override.txt"', + "Content-Type": "text/html", // Should use provided contentType + "Content-Length": "12", // blob.size + }); + }); + + it("should handle Blob with intrinsic type", async () => { + const blob = new Blob(["test content"], { type: "application/json" }); + const input: Uploadable.WithMetadata = { + data: blob, + filename: "data.json", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(blob); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="data.json"', + "Content-Type": "application/json", // Should use blob's type + "Content-Length": "12", // blob.size + }); + }); + + it("should handle Blob passed directly", async () => { + const blob = new Blob(["test content"], { type: "text/plain" }); + + const result = await toBinaryUploadRequest(blob); + + expect(result.body).toBe(blob); + expect(result.headers).toEqual({ + "Content-Type": "text/plain", // Should use blob's type + "Content-Length": "12", // blob.size + }); + }); + }); + + describe("File input", () => { + it("should handle File with metadata", async () => { + const file = new File(["file content"], "original.txt", { type: "text/plain" }); + const input: Uploadable.WithMetadata = { + data: file, + filename: "renamed.txt", + contentType: "text/html", // Override file's type + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(file); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="renamed.txt"', + "Content-Type": "text/html", // Should use provided contentType + "Content-Length": "12", // file.size + }); + }); + + it("should handle File with intrinsic properties", async () => { + const file = new File(["file content"], "test.json", { type: "application/json" }); + const input: Uploadable.WithMetadata = { + data: file, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(file); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="test.json"', // Should use file's name + "Content-Type": "application/json", // Should use file's type + "Content-Length": "12", // file.size + }); + }); + + it("should handle File passed directly", async () => { + const file = new File(["file content"], "direct.txt", { type: "text/plain" }); + + const result = await toBinaryUploadRequest(file); + + expect(result.body).toBe(file); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="direct.txt"', + "Content-Type": "text/plain", + "Content-Length": "12", // file.size + }); + }); + }); + + describe("ReadableStream input", () => { + it("should handle ReadableStream with metadata", async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("stream data")); + controller.close(); + }, + }); + const input: Uploadable.WithMetadata = { + data: stream, + filename: "stream.txt", + contentType: "text/plain", + contentLength: 100, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(stream); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="stream.txt"', + "Content-Type": "text/plain", + "Content-Length": "100", // Should use provided contentLength + }); + }); + + it("should handle ReadableStream without size", async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("stream data")); + controller.close(); + }, + }); + const input: Uploadable.WithMetadata = { + data: stream, + filename: "stream.txt", + contentType: "text/plain", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(stream); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="stream.txt"', + "Content-Type": "text/plain", + // No Content-Length header since it cannot be determined from ReadableStream + }); + }); + + it("should handle ReadableStream passed directly", async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("stream data")); + controller.close(); + }, + }); + + const result = await toBinaryUploadRequest(stream); + + expect(result.body).toBe(stream); + expect(result.headers).toEqual({ + // No headers since no metadata provided and cannot be determined + }); + }); + }); + + describe("Node.js Readable stream input", () => { + it("should handle Readable stream with metadata", async () => { + const readable = new Readable({ + read() { + this.push("readable data"); + this.push(null); + }, + }); + const input: Uploadable.WithMetadata = { + data: readable, + filename: "readable.txt", + contentType: "text/plain", + contentLength: 50, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(readable); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="readable.txt"', + "Content-Type": "text/plain", + "Content-Length": "50", // Should use provided contentLength + }); + }); + + it("should handle Readable stream without size", async () => { + const readable = new Readable({ + read() { + this.push("readable data"); + this.push(null); + }, + }); + const input: Uploadable.WithMetadata = { + data: readable, + filename: "readable.txt", + contentType: "text/plain", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(readable); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="readable.txt"', + "Content-Type": "text/plain", + // No Content-Length header since it cannot be determined from Readable + }); + }); + + it("should handle Readable stream passed directly", async () => { + const readable = new Readable({ + read() { + this.push("readable data"); + this.push(null); + }, + }); + + const result = await toBinaryUploadRequest(readable); + + expect(result.body).toBe(readable); + expect(result.headers).toEqual({ + // No headers since no metadata provided and cannot be determined + }); + }); + }); + + describe("File path input (FromPath type)", () => { + it("should handle file path with all metadata", async () => { + const input: Uploadable.FromPath = { + path: TEST_FILE_PATH, + filename: "custom.txt", + contentType: "text/html", + contentLength: 42, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBeInstanceOf(fs.ReadStream); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="custom.txt"', + "Content-Type": "text/html", + "Content-Length": "42", // Should use provided contentLength + }); + }); + + it("should handle file path with minimal metadata", async () => { + const input: Uploadable.FromPath = { + path: TEST_FILE_PATH, + contentType: "text/plain", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBeInstanceOf(fs.ReadStream); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="test-file.txt"', // Should extract from path + "Content-Type": "text/plain", + "Content-Length": "21", // Should determine from file system (test file is 21 bytes) + }); + }); + + it("should handle file path with no metadata", async () => { + const input: Uploadable.FromPath = { + path: TEST_FILE_PATH, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBeInstanceOf(fs.ReadStream); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="test-file.txt"', // Should extract from path + "Content-Length": "21", // Should determine from file system (test file is 21 bytes) + }); + }); + }); + + describe("ArrayBufferView input", () => { + it("should handle ArrayBufferView with metadata", async () => { + const arrayBuffer = new ArrayBuffer(10); + const arrayBufferView = new Int8Array(arrayBuffer); + const input: Uploadable.WithMetadata = { + data: arrayBufferView, + filename: "view.bin", + contentType: "application/octet-stream", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(arrayBufferView); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="view.bin"', + "Content-Type": "application/octet-stream", + "Content-Length": "10", // arrayBufferView.byteLength + }); + }); + + it("should handle ArrayBufferView passed directly", async () => { + const arrayBuffer = new ArrayBuffer(10); + const arrayBufferView = new Int8Array(arrayBuffer); + + const result = await toBinaryUploadRequest(arrayBufferView); + + expect(result.body).toBe(arrayBufferView); + expect(result.headers).toEqual({ + "Content-Length": "10", // arrayBufferView.byteLength + }); + }); + }); + + describe("Edge cases", () => { + it("should handle empty headers when no metadata is available", async () => { + const buffer = Buffer.from(""); + const input: Uploadable.WithMetadata = { + data: buffer, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(buffer); + expect(result.headers).toEqual({ + "Content-Length": "0", + }); + }); + + it("should handle zero contentLength", async () => { + const buffer = Buffer.from("test"); + const input: Uploadable.WithMetadata = { + data: buffer, + contentLength: 0, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(buffer); + expect(result.headers).toEqual({ + "Content-Length": "0", // Should use provided 0 + }); + }); + + it("should handle null filename", async () => { + const buffer = Buffer.from("test"); + const input: Uploadable.WithMetadata = { + data: buffer, + filename: undefined, + contentType: "text/plain", + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(buffer); + expect(result.headers).toEqual({ + "Content-Type": "text/plain", + "Content-Length": "4", + // No Content-Disposition since filename is undefined + }); + }); + + it("should handle null contentType", async () => { + const buffer = Buffer.from("test"); + const input: Uploadable.WithMetadata = { + data: buffer, + filename: "test.txt", + contentType: undefined, + }; + + const result = await toBinaryUploadRequest(input); + + expect(result.body).toBe(buffer); + expect(result.headers).toEqual({ + "Content-Disposition": 'attachment; filename="test.txt"', + "Content-Length": "4", + // No Content-Type since contentType is undefined + }); + }); + }); +}); diff --git a/skyvern-ts/client/tests/unit/form-data-utils/encodeAsFormParameter.test.ts b/skyvern-ts/client/tests/unit/form-data-utils/encodeAsFormParameter.test.ts new file mode 100644 index 00000000..d4b0c450 --- /dev/null +++ b/skyvern-ts/client/tests/unit/form-data-utils/encodeAsFormParameter.test.ts @@ -0,0 +1,344 @@ +import { encodeAsFormParameter } from "../../../src/core/form-data-utils/encodeAsFormParameter"; + +describe("encodeAsFormParameter", () => { + describe("Basic functionality", () => { + it("should return empty object for null/undefined", () => { + expect(encodeAsFormParameter(null)).toEqual({}); + expect(encodeAsFormParameter(undefined)).toEqual({}); + }); + + it("should return empty object for primitive values", () => { + expect(encodeAsFormParameter("hello")).toEqual({}); + expect(encodeAsFormParameter(42)).toEqual({}); + expect(encodeAsFormParameter(true)).toEqual({}); + }); + + it("should handle simple key-value pairs", () => { + const obj = { name: "John", age: 30 }; + expect(encodeAsFormParameter(obj)).toEqual({ + name: "John", + age: "30", + }); + }); + + it("should handle empty objects", () => { + expect(encodeAsFormParameter({})).toEqual({}); + }); + }); + + describe("Array handling", () => { + it("should handle arrays with indices format (default)", () => { + const obj = { items: ["a", "b", "c"] }; + expect(encodeAsFormParameter(obj)).toEqual({ + "items[0]": "a", + "items[1]": "b", + "items[2]": "c", + }); + }); + + it("should handle empty arrays", () => { + const obj = { items: [] }; + expect(encodeAsFormParameter(obj)).toEqual({}); + }); + + it("should handle arrays with mixed types", () => { + const obj = { mixed: ["string", 42, true, false] }; + expect(encodeAsFormParameter(obj)).toEqual({ + "mixed[0]": "string", + "mixed[1]": "42", + "mixed[2]": "true", + "mixed[3]": "false", + }); + }); + + it("should handle arrays with objects", () => { + const obj = { users: [{ name: "John" }, { name: "Jane" }] }; + expect(encodeAsFormParameter(obj)).toEqual({ + "users[0][name]": "John", + "users[1][name]": "Jane", + }); + }); + + it("should handle arrays with null/undefined values", () => { + const obj = { items: ["a", null, "c", undefined, "e"] }; + expect(encodeAsFormParameter(obj)).toEqual({ + "items[0]": "a", + "items[1]": "", + "items[2]": "c", + "items[4]": "e", + }); + }); + }); + + describe("Nested objects", () => { + it("should handle nested objects", () => { + const obj = { user: { name: "John", age: 30 } }; + expect(encodeAsFormParameter(obj)).toEqual({ + "user[name]": "John", + "user[age]": "30", + }); + }); + + it("should handle deeply nested objects", () => { + const obj = { user: { profile: { name: "John", settings: { theme: "dark" } } } }; + expect(encodeAsFormParameter(obj)).toEqual({ + "user[profile][name]": "John", + "user[profile][settings][theme]": "dark", + }); + }); + + it("should handle empty nested objects", () => { + const obj = { user: {} }; + expect(encodeAsFormParameter(obj)).toEqual({}); + }); + }); + + describe("Special characters and encoding", () => { + it("should not encode values (encode: false is used)", () => { + const obj = { name: "John Doe", email: "john@example.com" }; + expect(encodeAsFormParameter(obj)).toEqual({ + name: "John Doe", + email: "john@example.com", + }); + }); + + it("should not encode special characters in keys", () => { + const obj = { "user name": "John", "email[primary]": "john@example.com" }; + expect(encodeAsFormParameter(obj)).toEqual({ + "user name": "John", + "email[primary]": "john@example.com", + }); + }); + + it("should handle values that contain special characters", () => { + const obj = { + query: "search term with spaces", + filter: "category:electronics", + }; + expect(encodeAsFormParameter(obj)).toEqual({ + query: "search term with spaces", + filter: "category:electronics", + }); + }); + + it("should handle ampersand and equals characters (edge case)", () => { + // Note: Values containing & and = may be problematic because + // encodeAsFormParameter splits on these characters when parsing the stringified result + const obj = { + message: "Hello & welcome", + equation: "x = y + z", + }; + // This demonstrates the limitation - ampersands and equals signs in values + // will cause the parameter to be split incorrectly + const result = encodeAsFormParameter(obj); + + // We expect this to be parsed incorrectly due to the implementation + expect(result.message).toBe("Hello "); + expect(result[" welcome"]).toBeUndefined(); + expect(result.equation).toBe("x "); + expect(result[" y + z"]).toBeUndefined(); + }); + }); + + describe("Form data specific scenarios", () => { + it("should handle file upload metadata", () => { + const metadata = { + file: { + name: "document.pdf", + size: 1024, + type: "application/pdf", + }, + options: { + compress: true, + quality: 0.8, + }, + }; + expect(encodeAsFormParameter(metadata)).toEqual({ + "file[name]": "document.pdf", + "file[size]": "1024", + "file[type]": "application/pdf", + "options[compress]": "true", + "options[quality]": "0.8", + }); + }); + + it("should handle form validation data", () => { + const formData = { + fields: ["name", "email", "phone"], + validation: { + required: ["name", "email"], + patterns: { + email: "^[^@]+@[^@]+\\.[^@]+$", + phone: "^\\+?[1-9]\\d{1,14}$", + }, + }, + }; + expect(encodeAsFormParameter(formData)).toEqual({ + "fields[0]": "name", + "fields[1]": "email", + "fields[2]": "phone", + "validation[required][0]": "name", + "validation[required][1]": "email", + "validation[patterns][email]": "^[^@]+@[^@]+\\.[^@]+$", + "validation[patterns][phone]": "^\\+?[1-9]\\d{1,14}$", + }); + }); + + it("should handle search/filter parameters", () => { + const searchParams = { + filters: { + status: ["active", "pending"], + category: { + type: "electronics", + subcategories: ["phones", "laptops"], + }, + }, + sort: { field: "name", direction: "asc" }, + pagination: { page: 1, limit: 20 }, + }; + expect(encodeAsFormParameter(searchParams)).toEqual({ + "filters[status][0]": "active", + "filters[status][1]": "pending", + "filters[category][type]": "electronics", + "filters[category][subcategories][0]": "phones", + "filters[category][subcategories][1]": "laptops", + "sort[field]": "name", + "sort[direction]": "asc", + "pagination[page]": "1", + "pagination[limit]": "20", + }); + }); + }); + + describe("Edge cases", () => { + it("should handle boolean values", () => { + const obj = { enabled: true, disabled: false }; + expect(encodeAsFormParameter(obj)).toEqual({ + enabled: "true", + disabled: "false", + }); + }); + + it("should handle empty strings", () => { + const obj = { name: "", description: "test" }; + expect(encodeAsFormParameter(obj)).toEqual({ + name: "", + description: "test", + }); + }); + + it("should handle zero values", () => { + const obj = { count: 0, price: 0.0 }; + expect(encodeAsFormParameter(obj)).toEqual({ + count: "0", + price: "0", + }); + }); + + it("should handle numeric keys", () => { + const obj = { "0": "zero", "1": "one" }; + expect(encodeAsFormParameter(obj)).toEqual({ + "0": "zero", + "1": "one", + }); + }); + + it("should handle objects with null/undefined values", () => { + const obj = { name: "John", age: null, email: undefined, active: true }; + expect(encodeAsFormParameter(obj)).toEqual({ + name: "John", + age: "", + active: "true", + }); + }); + }); + + describe("Integration with form submission", () => { + it("should produce form-compatible key-value pairs", () => { + const formObject = { + username: "john_doe", + preferences: { + theme: "dark", + notifications: ["email", "push"], + settings: { + autoSave: true, + timeout: 300, + }, + }, + }; + + const result = encodeAsFormParameter(formObject); + + // Verify all values are strings (as required for form data) + Object.values(result).forEach((value) => { + expect(typeof value).toBe("string"); + }); + + // Verify the structure can be reconstructed + expect(result).toEqual({ + username: "john_doe", + "preferences[theme]": "dark", + "preferences[notifications][0]": "email", + "preferences[notifications][1]": "push", + "preferences[settings][autoSave]": "true", + "preferences[settings][timeout]": "300", + }); + }); + + it("should handle complex nested arrays for API parameters", () => { + const apiParams = { + query: { + filters: [ + { field: "status", operator: "eq", value: "active" }, + { field: "created", operator: "gte", value: "2023-01-01" }, + ], + sort: [ + { field: "name", direction: "asc" }, + { field: "created", direction: "desc" }, + ], + }, + }; + + const result = encodeAsFormParameter(apiParams); + expect(result).toEqual({ + "query[filters][0][field]": "status", + "query[filters][0][operator]": "eq", + "query[filters][0][value]": "active", + "query[filters][1][field]": "created", + "query[filters][1][operator]": "gte", + "query[filters][1][value]": "2023-01-01", + "query[sort][0][field]": "name", + "query[sort][0][direction]": "asc", + "query[sort][1][field]": "created", + "query[sort][1][direction]": "desc", + }); + }); + }); + + describe("Error cases and malformed input", () => { + it("should handle circular references gracefully", () => { + const obj: any = { name: "test" }; + obj.self = obj; + + // This will throw a RangeError due to stack overflow - this is expected behavior + expect(() => encodeAsFormParameter(obj)).toThrow("Maximum call stack size exceeded"); + }); + + it("should handle very deeply nested objects", () => { + let deepObj: any = { value: "deep" }; + for (let i = 0; i < 100; i++) { + deepObj = { level: deepObj }; + } + + expect(() => encodeAsFormParameter(deepObj)).not.toThrow(); + const result = encodeAsFormParameter(deepObj); + expect(Object.keys(result).length).toBeGreaterThan(0); + }); + + it("should handle empty string splitting edge case", () => { + // Test what happens when qs returns an empty string + const result = encodeAsFormParameter({}); + expect(result).toEqual({}); + }); + }); +}); diff --git a/skyvern-ts/client/tests/unit/form-data-utils/formDataWrapper.test.ts b/skyvern-ts/client/tests/unit/form-data-utils/formDataWrapper.test.ts new file mode 100644 index 00000000..47705084 --- /dev/null +++ b/skyvern-ts/client/tests/unit/form-data-utils/formDataWrapper.test.ts @@ -0,0 +1,346 @@ +import { Blob, File } from "buffer"; +import { join } from "path"; +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { Readable } from "stream"; +import { FormDataWrapper, newFormData } from "../../../src/core/form-data-utils/FormDataWrapper"; + +// Helper function to serialize FormData to string for inspection +async function serializeFormData(formData: FormData): Promise { + const request = new Request("http://localhost", { + method: "POST", + body: formData, + }); + + const buffer = await request.arrayBuffer(); + return new TextDecoder().decode(buffer); +} + +describe("FormDataWrapper", () => { + let formData: FormDataWrapper; + + beforeEach(async () => { + formData = new FormDataWrapper(); + await formData.setup(); + }); + + it("Upload file by path", async () => { + await formData.appendFile("file", { + path: join(__dirname, "..", "test-file.txt"), + }); + + const serialized = await serializeFormData(formData.getRequest().body); + + expect(serialized).toContain('Content-Disposition: form-data; name="file"'); + expect(serialized).toContain('filename="test-file.txt"'); + expect(serialized).toContain("This is a test file!"); + }); + + it("Upload file by path with filename", async () => { + await formData.appendFile("file", { + path: join(__dirname, "..", "test-file.txt"), + filename: "custom-file.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + + expect(serialized).toContain('Content-Disposition: form-data; name="file"'); + expect(serialized).toContain('filename="custom-file.txt"'); + expect(serialized).toContain("This is a test file!"); + }); + + describe("Stream handling", () => { + it("serializes Node.js Readable stream with filename", async () => { + const stream = Readable.from(["file content"]); + await formData.appendFile("file", { + data: stream, + filename: "testfile.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + + expect(serialized).toContain('Content-Disposition: form-data; name="file"'); + expect(serialized).toContain('filename="testfile.txt"'); + expect(serialized).toContain("file content"); + }); + + it("auto-detects filename from stream path property", async () => { + const stream = Readable.from(["file content"]); + (stream as { path?: string }).path = "/test/path/testfile.txt"; + + await formData.appendFile("file", stream); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="testfile.txt"'); + }); + + it("handles Windows-style paths", async () => { + const stream = Readable.from(["file content"]); + (stream as { path?: string }).path = "C:\\test\\path\\testfile.txt"; + + await formData.appendFile("file", stream); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="testfile.txt"'); + }); + + it("handles empty streams", async () => { + const stream = Readable.from([]); + await formData.appendFile("file", { + data: stream, + filename: "empty.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="empty.txt"'); + expect(serialized).toMatch(/------formdata-undici-\w+|------WebKitFormBoundary\w+/); + }); + + it("serializes Web ReadableStream with filename", async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("web stream content")); + controller.close(); + }, + }); + + await formData.appendFile("file", { + data: stream, + filename: "webstream.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="webstream.txt"'); + expect(serialized).toContain("web stream content"); + }); + + it("handles empty Web ReadableStream", async () => { + const stream = new ReadableStream({ + start(controller) { + controller.close(); + }, + }); + + await formData.appendFile("file", { + data: stream, + filename: "empty.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="empty.txt"'); + expect(serialized).toMatch(/------formdata-undici-\w+|------WebKitFormBoundary\w+/); + }); + }); + + describe("Blob and File types", () => { + it("serializes Blob with specified filename", async () => { + const blob = new Blob(["file content"], { type: "text/plain" }); + await formData.appendFile("file", { + data: blob, + filename: "testfile.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="testfile.txt"'); + expect(serialized).toContain("Content-Type: text/plain"); + expect(serialized).toContain("file content"); + }); + + it("uses default filename for Blob without explicit filename", async () => { + const blob = new Blob(["file content"], { type: "text/plain" }); + await formData.appendFile("file", blob); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="blob"'); + }); + + it("preserves File object filename", async () => { + if (typeof File !== "undefined") { + const file = new File(["file content"], "original.txt", { type: "text/plain" }); + await formData.appendFile("file", file); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="original.txt"'); + expect(serialized).toContain("file content"); + } + }); + + it("allows filename override for File objects", async () => { + if (typeof File !== "undefined") { + const file = new File(["file content"], "original.txt", { type: "text/plain" }); + await formData.appendFile("file", { + data: file, + filename: "override.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="override.txt"'); + expect(serialized).not.toContain('filename="original.txt"'); + } + }); + }); + + describe("Binary data types", () => { + it("serializes ArrayBuffer with filename", async () => { + const arrayBuffer = new ArrayBuffer(8); + new Uint8Array(arrayBuffer).set([1, 2, 3, 4, 5, 6, 7, 8]); + + await formData.appendFile("file", { + data: arrayBuffer, + filename: "binary.bin", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="binary.bin"'); + expect(serialized).toMatch(/------formdata-undici-\w+|------WebKitFormBoundary\w+/); + }); + + it("serializes Uint8Array with filename", async () => { + const uint8Array = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" + await formData.appendFile("file", { + data: uint8Array, + filename: "binary.bin", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="binary.bin"'); + expect(serialized).toContain("Hello"); + }); + + it("serializes other typed arrays", async () => { + const int16Array = new Int16Array([1000, 2000, 3000]); + await formData.appendFile("file", { + data: int16Array, + filename: "numbers.bin", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="numbers.bin"'); + }); + + it("serializes Buffer data with filename", async () => { + if (typeof Buffer !== "undefined" && typeof Buffer.isBuffer === "function") { + const buffer = Buffer.from("test content"); + await formData.appendFile("file", { + data: buffer, + filename: "test.txt", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="test.txt"'); + expect(serialized).toContain("test content"); + } + }); + }); + + describe("Text and primitive types", () => { + it("serializes string as regular form field", async () => { + formData.append("text", "test string"); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('name="text"'); + expect(serialized).not.toContain("filename="); + expect(serialized).toContain("test string"); + }); + + it("serializes numbers and booleans as strings", async () => { + formData.append("number", 12345); + formData.append("flag", true); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain("12345"); + expect(serialized).toContain("true"); + }); + }); + + describe("Edge cases and error handling", () => { + it("handles empty filename gracefully", async () => { + await formData.appendFile("file", { + data: new Blob(["content"], { type: "text/plain" }), + filename: "", + }); + + const serialized = await serializeFormData(formData.getRequest().body); + expect(serialized).toContain('filename="blob"'); // Default fallback + }); + + it("handles multiple files in single form", async () => { + await formData.appendFile("file1", { + data: new Blob(["content1"], { type: "text/plain" }), + filename: "file1.txt", + }); + await formData.appendFile("file2", { + data: new Blob(["content2"], { type: "text/plain" }), + filename: "file2.txt", + }); + formData.append("text", "regular field"); + + const serialized = await serializeFormData(formData.getRequest().body); + + expect(serialized).toContain('filename="file1.txt"'); + expect(serialized).toContain('filename="file2.txt"'); + expect(serialized).toContain('name="text"'); + expect(serialized).not.toContain('filename="text"'); + }); + }); + + describe("Request structure", () => { + it("returns correct request structure", async () => { + await formData.appendFile("file", { + data: new Blob(["content"], { type: "text/plain" }), + filename: "test.txt", + }); + + const request = formData.getRequest(); + + expect(request).toHaveProperty("body"); + expect(request).toHaveProperty("headers"); + expect(request).toHaveProperty("duplex"); + expect(request.body).toBeInstanceOf(FormData); + expect(request.headers).toEqual({}); + expect(request.duplex).toBe("half"); + }); + + it("generates proper multipart boundary structure", async () => { + await formData.appendFile("file", { + data: new Blob(["test content"], { type: "text/plain" }), + filename: "test.txt", + }); + formData.append("field", "value"); + + const serialized = await serializeFormData(formData.getRequest().body); + + expect(serialized).toMatch(/------formdata-undici-\w+|------WebKitFormBoundary\w+/); + expect(serialized).toContain("Content-Disposition: form-data;"); + expect(serialized).toMatch(/------formdata-undici-\w+--|------WebKitFormBoundary\w+--/); + }); + }); + + describe("Factory function", () => { + it("returns FormDataWrapper instance", async () => { + const formData = await newFormData(); + expect(formData).toBeInstanceOf(FormDataWrapper); + }); + + it("creates independent instances", async () => { + const formData1 = await newFormData(); + const formData2 = await newFormData(); + + await formData1.setup(); + await formData2.setup(); + + formData1.append("test1", "value1"); + formData2.append("test2", "value2"); + + const request1 = formData1.getRequest() as { body: FormData }; + const request2 = formData2.getRequest() as { body: FormData }; + + const entries1 = Array.from(request1.body.entries()); + const entries2 = Array.from(request2.body.entries()); + + expect(entries1).toHaveLength(1); + expect(entries2).toHaveLength(1); + expect(entries1[0][0]).toBe("test1"); + expect(entries2[0][0]).toBe("test2"); + }); + }); +}); diff --git a/skyvern-ts/client/tests/unit/test-file.txt b/skyvern-ts/client/tests/unit/test-file.txt new file mode 100644 index 00000000..c66d471e --- /dev/null +++ b/skyvern-ts/client/tests/unit/test-file.txt @@ -0,0 +1 @@ +This is a test file! diff --git a/skyvern-ts/client/tests/wire/main.test.ts b/skyvern-ts/client/tests/wire/main.test.ts index c8405105..9a8c48bd 100644 --- a/skyvern-ts/client/tests/wire/main.test.ts +++ b/skyvern-ts/client/tests/wire/main.test.ts @@ -1207,7 +1207,7 @@ describe("SkyvernClient", () => { .build(); await expect(async () => { - return await client.retryRunWebhook("run_id"); + return await client.retryRunWebhook("run_id", undefined); }).rejects.toThrow(Skyvern.UnprocessableEntityError); }); diff --git a/skyvern-ts/client/tests/wire/workflows.test.ts b/skyvern-ts/client/tests/wire/workflows.test.ts deleted file mode 100644 index 8cc25256..00000000 --- a/skyvern-ts/client/tests/wire/workflows.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import * as Skyvern from "../../src/api/index"; -import { SkyvernClient } from "../../src/Client"; -import { mockServerPool } from "../mock-server/MockServerPool"; - -describe("Workflows", () => { - test("setWorkflowTemplateStatus (1)", async () => { - const server = mockServerPool.createServer(); - const client = new SkyvernClient({ apiKey: "test", environment: server.baseUrl }); - - const rawResponseBody = { key: "value" }; - server - .mockEndpoint() - .put("/v1/workflows/workflow_permanent_id/template") - .respondWith() - .statusCode(200) - .jsonBody(rawResponseBody) - .build(); - - const response = await client.workflows.setWorkflowTemplateStatus("workflow_permanent_id", { - is_template: true, - }); - expect(response).toEqual({ - key: "value", - }); - }); - - test("setWorkflowTemplateStatus (2)", async () => { - const server = mockServerPool.createServer(); - const client = new SkyvernClient({ apiKey: "test", environment: server.baseUrl }); - - const rawResponseBody = { key: "value" }; - server - .mockEndpoint() - .put("/v1/workflows/workflow_permanent_id/template") - .respondWith() - .statusCode(422) - .jsonBody(rawResponseBody) - .build(); - - await expect(async () => { - return await client.workflows.setWorkflowTemplateStatus("workflow_permanent_id", { - is_template: true, - }); - }).rejects.toThrow(Skyvern.UnprocessableEntityError); - }); -}); diff --git a/skyvern/client/__init__.py b/skyvern/client/__init__.py index 2fdb2b0a..6b25f479 100644 --- a/skyvern/client/__init__.py +++ b/skyvern/client/__init__.py @@ -283,6 +283,7 @@ if typing.TYPE_CHECKING: PromptAction, PromptBranchCriteria, ProxyLocation, + RetryRunWebhookRequest, RunEngine, RunSdkActionRequestAction, RunSdkActionRequestAction_AiAct, @@ -352,6 +353,7 @@ if typing.TYPE_CHECKING: TotpType, UploadFileAction, UploadFileActionData, + UploadFileResponse, UploadToS3Block, UploadToS3BlockYaml, UrlBlock, @@ -496,7 +498,7 @@ if typing.TYPE_CHECKING: WorkflowStatus, ) from .errors import BadRequestError, ConflictError, ForbiddenError, NotFoundError, UnprocessableEntityError - from . import scripts, workflows + from . import scripts from .client import AsyncSkyvern, Skyvern from .environment import SkyvernEnvironment from .version import __version__ @@ -782,6 +784,7 @@ _dynamic_imports: typing.Dict[str, str] = { "PromptAction": ".types", "PromptBranchCriteria": ".types", "ProxyLocation": ".types", + "RetryRunWebhookRequest": ".types", "RunEngine": ".types", "RunSdkActionRequestAction": ".types", "RunSdkActionRequestAction_AiAct": ".types", @@ -854,6 +857,7 @@ _dynamic_imports: typing.Dict[str, str] = { "UnprocessableEntityError": ".errors", "UploadFileAction": ".types", "UploadFileActionData": ".types", + "UploadFileResponse": ".types", "UploadToS3Block": ".types", "UploadToS3BlockYaml": ".types", "UrlBlock": ".types", @@ -998,7 +1002,6 @@ _dynamic_imports: typing.Dict[str, str] = { "WorkflowStatus": ".types", "__version__": ".version", "scripts": ".scripts", - "workflows": ".workflows", } @@ -1305,6 +1308,7 @@ __all__ = [ "PromptAction", "PromptBranchCriteria", "ProxyLocation", + "RetryRunWebhookRequest", "RunEngine", "RunSdkActionRequestAction", "RunSdkActionRequestAction_AiAct", @@ -1377,6 +1381,7 @@ __all__ = [ "UnprocessableEntityError", "UploadFileAction", "UploadFileActionData", + "UploadFileResponse", "UploadToS3Block", "UploadToS3BlockYaml", "UrlBlock", @@ -1521,5 +1526,4 @@ __all__ = [ "WorkflowStatus", "__version__", "scripts", - "workflows", ] diff --git a/skyvern/client/client.py b/skyvern/client/client.py index 79c895a9..cfed952d 100644 --- a/skyvern/client/client.py +++ b/skyvern/client/client.py @@ -6,6 +6,7 @@ import datetime as dt import typing import httpx +from . import core from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from .core.request_options import RequestOptions from .environment import SkyvernEnvironment @@ -19,6 +20,7 @@ from .types.create_script_response import CreateScriptResponse from .types.credential_response import CredentialResponse from .types.get_run_response import GetRunResponse from .types.proxy_location import ProxyLocation +from .types.retry_run_webhook_request import RetryRunWebhookRequest from .types.run_engine import RunEngine from .types.run_sdk_action_request_action import RunSdkActionRequestAction from .types.run_sdk_action_response import RunSdkActionResponse @@ -30,6 +32,7 @@ from .types.task_run_request_data_extraction_schema import TaskRunRequestDataExt from .types.task_run_request_proxy_location import TaskRunRequestProxyLocation from .types.task_run_response import TaskRunResponse from .types.totp_code import TotpCode +from .types.upload_file_response import UploadFileResponse from .types.workflow import Workflow from .types.workflow_create_yaml_request import WorkflowCreateYamlRequest from .types.workflow_run_request_proxy_location import WorkflowRunRequestProxyLocation @@ -39,7 +42,6 @@ from .types.workflow_status import WorkflowStatus if typing.TYPE_CHECKING: from .scripts.client import AsyncScriptsClient, ScriptsClient - from .workflows.client import AsyncWorkflowsClient, WorkflowsClient # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -110,7 +112,6 @@ class Skyvern: timeout=_defaulted_timeout, ) self._raw_client = RawSkyvern(client_wrapper=self._client_wrapper) - self._workflows: typing.Optional[WorkflowsClient] = None self._scripts: typing.Optional[ScriptsClient] = None @property @@ -793,7 +794,7 @@ class Skyvern: self, run_id: str, *, - webhook_url: typing.Optional[str] = None, + request: typing.Optional[RetryRunWebhookRequest] = None, request_options: typing.Optional[RequestOptions] = None, ) -> typing.Optional[typing.Any]: """ @@ -804,6 +805,8 @@ class Skyvern: run_id : str The id of the task run or the workflow run. + request : typing.Optional[RetryRunWebhookRequest] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -814,16 +817,17 @@ class Skyvern: Examples -------- - from skyvern import Skyvern + from skyvern import RetryRunWebhookRequest, Skyvern client = Skyvern( api_key="YOUR_API_KEY", ) client.retry_run_webhook( run_id="tsk_123", + request=RetryRunWebhookRequest(), ) """ - _response = self._raw_client.retry_run_webhook(run_id, webhook_url=webhook_url, request_options=request_options) + _response = self._raw_client.retry_run_webhook(run_id, request=request, request_options=request_options) return _response.data def get_run_timeline( @@ -859,6 +863,35 @@ class Skyvern: _response = self._raw_client.get_run_timeline(run_id, request_options=request_options) return _response.data + def upload_file( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> UploadFileResponse: + """ + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UploadFileResponse + Successful Response + + Examples + -------- + from skyvern import Skyvern + + client = Skyvern( + api_key="YOUR_API_KEY", + ) + client.upload_file() + """ + _response = self._raw_client.upload_file(file=file, request_options=request_options) + return _response.data + def list_browser_profiles( self, *, include_deleted: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None ) -> typing.List[BrowserProfile]: @@ -1860,14 +1893,6 @@ class Skyvern: ) return _response.data - @property - def workflows(self): - if self._workflows is None: - from .workflows.client import WorkflowsClient # noqa: E402 - - self._workflows = WorkflowsClient(client_wrapper=self._client_wrapper) - return self._workflows - @property def scripts(self): if self._scripts is None: @@ -1943,7 +1968,6 @@ class AsyncSkyvern: timeout=_defaulted_timeout, ) self._raw_client = AsyncRawSkyvern(client_wrapper=self._client_wrapper) - self._workflows: typing.Optional[AsyncWorkflowsClient] = None self._scripts: typing.Optional[AsyncScriptsClient] = None @property @@ -2708,7 +2732,7 @@ class AsyncSkyvern: self, run_id: str, *, - webhook_url: typing.Optional[str] = None, + request: typing.Optional[RetryRunWebhookRequest] = None, request_options: typing.Optional[RequestOptions] = None, ) -> typing.Optional[typing.Any]: """ @@ -2719,6 +2743,8 @@ class AsyncSkyvern: run_id : str The id of the task run or the workflow run. + request : typing.Optional[RetryRunWebhookRequest] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2731,7 +2757,7 @@ class AsyncSkyvern: -------- import asyncio - from skyvern import AsyncSkyvern + from skyvern import AsyncSkyvern, RetryRunWebhookRequest client = AsyncSkyvern( api_key="YOUR_API_KEY", @@ -2741,14 +2767,13 @@ class AsyncSkyvern: async def main() -> None: await client.retry_run_webhook( run_id="tsk_123", + request=RetryRunWebhookRequest(), ) asyncio.run(main()) """ - _response = await self._raw_client.retry_run_webhook( - run_id, webhook_url=webhook_url, request_options=request_options - ) + _response = await self._raw_client.retry_run_webhook(run_id, request=request, request_options=request_options) return _response.data async def get_run_timeline( @@ -2792,6 +2817,43 @@ class AsyncSkyvern: _response = await self._raw_client.get_run_timeline(run_id, request_options=request_options) return _response.data + async def upload_file( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> UploadFileResponse: + """ + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UploadFileResponse + Successful Response + + Examples + -------- + import asyncio + + from skyvern import AsyncSkyvern + + client = AsyncSkyvern( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.upload_file() + + + asyncio.run(main()) + """ + _response = await self._raw_client.upload_file(file=file, request_options=request_options) + return _response.data + async def list_browser_profiles( self, *, include_deleted: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None ) -> typing.List[BrowserProfile]: @@ -3957,14 +4019,6 @@ class AsyncSkyvern: ) return _response.data - @property - def workflows(self): - if self._workflows is None: - from .workflows.client import AsyncWorkflowsClient # noqa: E402 - - self._workflows = AsyncWorkflowsClient(client_wrapper=self._client_wrapper) - return self._workflows - @property def scripts(self): if self._scripts is None: diff --git a/skyvern/client/core/client_wrapper.py b/skyvern/client/core/client_wrapper.py index 7513f68e..50605e7b 100644 --- a/skyvern/client/core/client_wrapper.py +++ b/skyvern/client/core/client_wrapper.py @@ -22,10 +22,10 @@ class BaseClientWrapper: def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { - "User-Agent": "skyvern/1.0.3", + "User-Agent": "skyvern/1.0.6", "X-Fern-Language": "Python", "X-Fern-SDK-Name": "skyvern", - "X-Fern-SDK-Version": "1.0.3", + "X-Fern-SDK-Version": "1.0.6", **(self.get_custom_headers() or {}), } if self._api_key is not None: diff --git a/skyvern/client/raw_client.py b/skyvern/client/raw_client.py index 79827801..692c7172 100644 --- a/skyvern/client/raw_client.py +++ b/skyvern/client/raw_client.py @@ -4,6 +4,7 @@ import datetime as dt import typing from json.decoder import JSONDecodeError +from . import core from .core.api_error import ApiError from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from .core.http_response import AsyncHttpResponse, HttpResponse @@ -25,6 +26,7 @@ from .types.create_script_response import CreateScriptResponse from .types.credential_response import CredentialResponse from .types.get_run_response import GetRunResponse from .types.proxy_location import ProxyLocation +from .types.retry_run_webhook_request import RetryRunWebhookRequest from .types.run_engine import RunEngine from .types.run_sdk_action_request_action import RunSdkActionRequestAction from .types.run_sdk_action_response import RunSdkActionResponse @@ -36,6 +38,7 @@ from .types.task_run_request_data_extraction_schema import TaskRunRequestDataExt from .types.task_run_request_proxy_location import TaskRunRequestProxyLocation from .types.task_run_response import TaskRunResponse from .types.totp_code import TotpCode +from .types.upload_file_response import UploadFileResponse from .types.workflow import Workflow from .types.workflow_create_yaml_request import WorkflowCreateYamlRequest from .types.workflow_run_request_proxy_location import WorkflowRunRequestProxyLocation @@ -960,7 +963,7 @@ class RawSkyvern: self, run_id: str, *, - webhook_url: typing.Optional[str] = None, + request: typing.Optional[RetryRunWebhookRequest] = None, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[typing.Optional[typing.Any]]: """ @@ -971,6 +974,8 @@ class RawSkyvern: run_id : str The id of the task run or the workflow run. + request : typing.Optional[RetryRunWebhookRequest] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -979,18 +984,17 @@ class RawSkyvern: HttpResponse[typing.Optional[typing.Any]] Successful Response """ - request_kwargs: dict[str, typing.Any] = {} - if webhook_url is not None: - request_kwargs = { - "json": {"webhook_url": webhook_url}, - "headers": {"content-type": "application/json"}, - "omit": OMIT, - } _response = self._client_wrapper.httpx_client.request( f"v1/runs/{jsonable_encoder(run_id)}/retry_webhook", method="POST", + json=convert_and_respect_annotation_metadata( + object_=request, annotation=RetryRunWebhookRequest, direction="write" + ), + headers={ + "content-type": "application/json", + }, request_options=request_options, - **request_kwargs, + omit=OMIT, ) try: if _response is None or not _response.text.strip(): @@ -1092,6 +1096,60 @@ class RawSkyvern: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def upload_file( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[UploadFileResponse]: + """ + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[UploadFileResponse] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + "v1/upload_file", + method="POST", + data={}, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UploadFileResponse, + parse_obj_as( + type_=UploadFileResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def list_browser_profiles( self, *, include_deleted: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[typing.List[BrowserProfile]]: @@ -3492,7 +3550,7 @@ class AsyncRawSkyvern: self, run_id: str, *, - webhook_url: typing.Optional[str] = None, + request: typing.Optional[RetryRunWebhookRequest] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[typing.Optional[typing.Any]]: """ @@ -3503,6 +3561,8 @@ class AsyncRawSkyvern: run_id : str The id of the task run or the workflow run. + request : typing.Optional[RetryRunWebhookRequest] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -3511,18 +3571,17 @@ class AsyncRawSkyvern: AsyncHttpResponse[typing.Optional[typing.Any]] Successful Response """ - request_kwargs: dict[str, typing.Any] = {} - if webhook_url is not None: - request_kwargs = { - "json": {"webhook_url": webhook_url}, - "headers": {"content-type": "application/json"}, - "omit": OMIT, - } _response = await self._client_wrapper.httpx_client.request( f"v1/runs/{jsonable_encoder(run_id)}/retry_webhook", method="POST", + json=convert_and_respect_annotation_metadata( + object_=request, annotation=RetryRunWebhookRequest, direction="write" + ), + headers={ + "content-type": "application/json", + }, request_options=request_options, - **request_kwargs, + omit=OMIT, ) try: if _response is None or not _response.text.strip(): @@ -3624,6 +3683,60 @@ class AsyncRawSkyvern: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def upload_file( + self, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[UploadFileResponse]: + """ + Parameters + ---------- + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[UploadFileResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/upload_file", + method="POST", + data={}, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UploadFileResponse, + parse_obj_as( + type_=UploadFileResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def list_browser_profiles( self, *, include_deleted: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[typing.List[BrowserProfile]]: diff --git a/skyvern/client/types/__init__.py b/skyvern/client/types/__init__.py index efa99655..f18c4ed2 100644 --- a/skyvern/client/types/__init__.py +++ b/skyvern/client/types/__init__.py @@ -310,6 +310,7 @@ if typing.TYPE_CHECKING: from .prompt_action import PromptAction from .prompt_branch_criteria import PromptBranchCriteria from .proxy_location import ProxyLocation + from .retry_run_webhook_request import RetryRunWebhookRequest from .run_engine import RunEngine from .run_sdk_action_request_action import ( RunSdkActionRequestAction, @@ -385,6 +386,7 @@ if typing.TYPE_CHECKING: from .totp_type import TotpType from .upload_file_action import UploadFileAction from .upload_file_action_data import UploadFileActionData + from .upload_file_response import UploadFileResponse from .upload_to_s3block import UploadToS3Block from .upload_to_s3block_yaml import UploadToS3BlockYaml from .url_block import UrlBlock @@ -818,6 +820,7 @@ _dynamic_imports: typing.Dict[str, str] = { "PromptAction": ".prompt_action", "PromptBranchCriteria": ".prompt_branch_criteria", "ProxyLocation": ".proxy_location", + "RetryRunWebhookRequest": ".retry_run_webhook_request", "RunEngine": ".run_engine", "RunSdkActionRequestAction": ".run_sdk_action_request_action", "RunSdkActionRequestAction_AiAct": ".run_sdk_action_request_action", @@ -887,6 +890,7 @@ _dynamic_imports: typing.Dict[str, str] = { "TotpType": ".totp_type", "UploadFileAction": ".upload_file_action", "UploadFileActionData": ".upload_file_action_data", + "UploadFileResponse": ".upload_file_response", "UploadToS3Block": ".upload_to_s3block", "UploadToS3BlockYaml": ".upload_to_s3block_yaml", "UrlBlock": ".url_block", @@ -1330,6 +1334,7 @@ __all__ = [ "PromptAction", "PromptBranchCriteria", "ProxyLocation", + "RetryRunWebhookRequest", "RunEngine", "RunSdkActionRequestAction", "RunSdkActionRequestAction_AiAct", @@ -1399,6 +1404,7 @@ __all__ = [ "TotpType", "UploadFileAction", "UploadFileActionData", + "UploadFileResponse", "UploadToS3Block", "UploadToS3BlockYaml", "UrlBlock", diff --git a/skyvern/client/types/prompt_action.py b/skyvern/client/types/prompt_action.py index c494ade5..9b9c2110 100644 --- a/skyvern/client/types/prompt_action.py +++ b/skyvern/client/types/prompt_action.py @@ -3,9 +3,7 @@ import typing import pydantic -import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata class PromptAction(UniversalBaseModel): @@ -18,9 +16,7 @@ class PromptAction(UniversalBaseModel): The prompt to send to the LLM """ - schema_: typing_extensions.Annotated[ - typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]], FieldMetadata(alias="schema") - ] = pydantic.Field(default=None) + response_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = pydantic.Field(default=None) """ Optional JSON schema to structure the response """ diff --git a/skyvern/client/types/retry_run_webhook_request.py b/skyvern/client/types/retry_run_webhook_request.py new file mode 100644 index 00000000..e16ac938 --- /dev/null +++ b/skyvern/client/types/retry_run_webhook_request.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RetryRunWebhookRequest(UniversalBaseModel): + webhook_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Optional webhook URL to send the payload to instead of the stored configuration + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/skyvern/client/types/run_sdk_action_request_action.py b/skyvern/client/types/run_sdk_action_request_action.py index d591075d..9b5897a2 100644 --- a/skyvern/client/types/run_sdk_action_request_action.py +++ b/skyvern/client/types/run_sdk_action_request_action.py @@ -5,9 +5,7 @@ from __future__ import annotations import typing import pydantic -import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata from .act_action_data import ActActionData from .click_action_data import ClickActionData from .extract_action_data import ExtractActionData @@ -172,9 +170,7 @@ class RunSdkActionRequestAction_Prompt(UniversalBaseModel): type: typing.Literal["prompt"] = "prompt" prompt: str - schema_: typing_extensions.Annotated[ - typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]], FieldMetadata(alias="schema") - ] = None + response_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None model: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None if IS_PYDANTIC_V2: diff --git a/skyvern/client/types/upload_file_response.py b/skyvern/client/types/upload_file_response.py new file mode 100644 index 00000000..5778fb01 --- /dev/null +++ b/skyvern/client/types/upload_file_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class UploadFileResponse(UniversalBaseModel): + s3uri: typing_extensions.Annotated[str, FieldMetadata(alias="s3_uri")] = pydantic.Field() + """ + S3 URI where the file was uploaded + """ + + presigned_url: str = pydantic.Field() + """ + Presigned URL to access the uploaded file + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/skyvern/client/workflows/__init__.py b/skyvern/client/workflows/__init__.py deleted file mode 100644 index 5cde0202..00000000 --- a/skyvern/client/workflows/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -# isort: skip_file - diff --git a/skyvern/client/workflows/client.py b/skyvern/client/workflows/client.py deleted file mode 100644 index 46edb7a0..00000000 --- a/skyvern/client/workflows/client.py +++ /dev/null @@ -1,127 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.request_options import RequestOptions -from .raw_client import AsyncRawWorkflowsClient, RawWorkflowsClient - - -class WorkflowsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._raw_client = RawWorkflowsClient(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> RawWorkflowsClient: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - RawWorkflowsClient - """ - return self._raw_client - - def set_workflow_template_status( - self, workflow_permanent_id: str, *, is_template: bool, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Optional[typing.Any]]: - """ - Set or unset a workflow as a template. - - Template status is stored at the workflow_permanent_id level (not per-version), - meaning all versions of a workflow share the same template status. - - Parameters - ---------- - workflow_permanent_id : str - - is_template : bool - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Optional[typing.Any]] - Successful Response - - Examples - -------- - from skyvern import Skyvern - - client = Skyvern( - api_key="YOUR_API_KEY", - ) - client.workflows.set_workflow_template_status( - workflow_permanent_id="workflow_permanent_id", - is_template=True, - ) - """ - _response = self._raw_client.set_workflow_template_status( - workflow_permanent_id, is_template=is_template, request_options=request_options - ) - return _response.data - - -class AsyncWorkflowsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._raw_client = AsyncRawWorkflowsClient(client_wrapper=client_wrapper) - - @property - def with_raw_response(self) -> AsyncRawWorkflowsClient: - """ - Retrieves a raw implementation of this client that returns raw responses. - - Returns - ------- - AsyncRawWorkflowsClient - """ - return self._raw_client - - async def set_workflow_template_status( - self, workflow_permanent_id: str, *, is_template: bool, request_options: typing.Optional[RequestOptions] = None - ) -> typing.Dict[str, typing.Optional[typing.Any]]: - """ - Set or unset a workflow as a template. - - Template status is stored at the workflow_permanent_id level (not per-version), - meaning all versions of a workflow share the same template status. - - Parameters - ---------- - workflow_permanent_id : str - - is_template : bool - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - typing.Dict[str, typing.Optional[typing.Any]] - Successful Response - - Examples - -------- - import asyncio - - from skyvern import AsyncSkyvern - - client = AsyncSkyvern( - api_key="YOUR_API_KEY", - ) - - - async def main() -> None: - await client.workflows.set_workflow_template_status( - workflow_permanent_id="workflow_permanent_id", - is_template=True, - ) - - - asyncio.run(main()) - """ - _response = await self._raw_client.set_workflow_template_status( - workflow_permanent_id, is_template=is_template, request_options=request_options - ) - return _response.data diff --git a/skyvern/client/workflows/raw_client.py b/skyvern/client/workflows/raw_client.py deleted file mode 100644 index c8fb091e..00000000 --- a/skyvern/client/workflows/raw_client.py +++ /dev/null @@ -1,136 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing -from json.decoder import JSONDecodeError - -from ..core.api_error import ApiError -from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper -from ..core.http_response import AsyncHttpResponse, HttpResponse -from ..core.jsonable_encoder import jsonable_encoder -from ..core.pydantic_utilities import parse_obj_as -from ..core.request_options import RequestOptions -from ..errors.unprocessable_entity_error import UnprocessableEntityError - - -class RawWorkflowsClient: - def __init__(self, *, client_wrapper: SyncClientWrapper): - self._client_wrapper = client_wrapper - - def set_workflow_template_status( - self, workflow_permanent_id: str, *, is_template: bool, request_options: typing.Optional[RequestOptions] = None - ) -> HttpResponse[typing.Dict[str, typing.Optional[typing.Any]]]: - """ - Set or unset a workflow as a template. - - Template status is stored at the workflow_permanent_id level (not per-version), - meaning all versions of a workflow share the same template status. - - Parameters - ---------- - workflow_permanent_id : str - - is_template : bool - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - HttpResponse[typing.Dict[str, typing.Optional[typing.Any]]] - Successful Response - """ - _response = self._client_wrapper.httpx_client.request( - f"v1/workflows/{jsonable_encoder(workflow_permanent_id)}/template", - method="PUT", - params={ - "is_template": is_template, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Optional[typing.Any]], - parse_obj_as( - type_=typing.Dict[str, typing.Optional[typing.Any]], # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 422: - raise UnprocessableEntityError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - -class AsyncRawWorkflowsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - - async def set_workflow_template_status( - self, workflow_permanent_id: str, *, is_template: bool, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[typing.Dict[str, typing.Optional[typing.Any]]]: - """ - Set or unset a workflow as a template. - - Template status is stored at the workflow_permanent_id level (not per-version), - meaning all versions of a workflow share the same template status. - - Parameters - ---------- - workflow_permanent_id : str - - is_template : bool - - request_options : typing.Optional[RequestOptions] - Request-specific configuration. - - Returns - ------- - AsyncHttpResponse[typing.Dict[str, typing.Optional[typing.Any]]] - Successful Response - """ - _response = await self._client_wrapper.httpx_client.request( - f"v1/workflows/{jsonable_encoder(workflow_permanent_id)}/template", - method="PUT", - params={ - "is_template": is_template, - }, - request_options=request_options, - ) - try: - if 200 <= _response.status_code < 300: - _data = typing.cast( - typing.Dict[str, typing.Optional[typing.Any]], - parse_obj_as( - type_=typing.Dict[str, typing.Optional[typing.Any]], # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 422: - raise UnprocessableEntityError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/uv.lock b/uv.lock index b5478b2b..46372eaf 100644 --- a/uv.lock +++ b/uv.lock @@ -5082,7 +5082,7 @@ wheels = [ [[package]] name = "skyvern" -version = "1.0.3" +version = "1.0.6" source = { editable = "." } dependencies = [ { name = "aioboto3" },