diff --git a/package-lock.json b/package-lock.json index e4f8a6b..4c86451 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,19 @@ { "name": "opencode-antigravity-auth", - "version": "1.3.3-beta.2", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencode-antigravity-auth", - "version": "1.3.3-beta.2", + "version": "1.5.1", "license": "MIT", "dependencies": { "@openauthjs/openauth": "^0.4.3", "@opencode-ai/plugin": "^0.15.30", + "fetch-socks": "^1.3.0", "proper-lockfile": "^4.1.2", + "undici": "^7.0.0", "xdg-basedir": "^5.1.0", "zod": "^4.0.0" }, @@ -33,8 +35,6 @@ }, "node_modules/@ampproject/remapping": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -47,8 +47,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -57,8 +55,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -66,13 +62,11 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -82,9 +76,7 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", "dev": true, "license": "MIT", "dependencies": { @@ -97,290 +89,167 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", "cpu": [ - "ppc64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "aix" + "win32" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], + "node_modules/@isaacs/cliui": { + "version": "8.0.2", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "node_modules/@openauthjs/openauth": { + "version": "0.4.3", + "dependencies": { + "@standard-schema/spec": "1.0.0-beta.3", + "aws4fetch": "1.0.20", + "jose": "5.9.6" + }, + "peerDependencies": { + "arctic": "^2.2.2", + "hono": "^4.0.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node_modules/@opencode-ai/plugin": { + "version": "0.15.31", + "dependencies": { + "@opencode-ai/sdk": "0.15.31", + "zod": "4.1.8" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@opencode-ai/plugin/node_modules/zod": { + "version": "4.1.8", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@opencode-ai/sdk": { + "version": "0.15.31" + }, + "node_modules/@oslojs/asn1": { + "version": "1.0.0", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@oslojs/binary": "1.0.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, + "node_modules/@oslojs/binary": { + "version": "1.0.0", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, + "node_modules/@oslojs/crypto": { + "version": "1.0.1", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@oslojs/asn1": "1.0.0", + "@oslojs/binary": "1.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, + "node_modules/@oslojs/jwt": { + "version": "0.2.0", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "@oslojs/encoding": "0.4.1" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], + "node_modules/@oslojs/jwt/node_modules/@oslojs/encoding": { + "version": "0.4.1", + "license": "MIT", + "peer": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=14" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", "cpu": [ "x64" ], @@ -388,1126 +257,851 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "win32" + ] }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0-beta.3", + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], + "node_modules/@types/deep-eql": { + "version": "4.0.2", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], + "node_modules/@types/estree": { + "version": "1.0.8", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/node": { + "version": "24.10.13", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "dependencies": { + "undici-types": "~7.16.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], + "node_modules/@types/proper-lockfile": { + "version": "4.1.4", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/retry": "*" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/retry": { + "version": "0.12.5", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], + "node_modules/@vitest/expect": { + "version": "3.2.4", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@vitest/mocker": { + "version": "3.2.4", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@vitest/runner": { + "version": "3.2.4", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@vitest/snapshot": { + "version": "3.2.4", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "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" - }, - "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==", + "node_modules/@vitest/spy": { + "version": "3.2.4", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@openauthjs/openauth": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@openauthjs/openauth/-/openauth-0.4.3.tgz", - "integrity": "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw==", + "node_modules/@vitest/ui": { + "version": "3.2.4", + "dev": true, + "license": "MIT", "dependencies": { - "@standard-schema/spec": "1.0.0-beta.3", - "aws4fetch": "1.0.20", - "jose": "5.9.6" + "@vitest/utils": "3.2.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.1", + "tinyglobby": "^0.2.14", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "arctic": "^2.2.2", - "hono": "^4.0.0" + "vitest": "3.2.4" } }, - "node_modules/@opencode-ai/plugin": { - "version": "0.15.31", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-0.15.31.tgz", - "integrity": "sha512-htKKCq9Htljf7vX5ANLDB7bU7TeJYrl8LP2CQUtCAguKUpVvpj5tiZ+edlCdhGFEqlpSp+pkiTEY5LCv1muowg==", + "node_modules/@vitest/utils": { + "version": "3.2.4", + "dev": true, + "license": "MIT", "dependencies": { - "@opencode-ai/sdk": "0.15.31", - "zod": "4.1.8" - } - }, - "node_modules/@opencode-ai/plugin/node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", - "license": "MIT", + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, "funding": { - "url": "https://github.com/sponsors/colinhacks" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@opencode-ai/sdk": { - "version": "0.15.31", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-0.15.31.tgz", - "integrity": "sha512-95HWBiNKQnwsubkR2E7QhBD/CH9yteZGrviWar0aKHWu8/RjWw9m7Znbv8DI+y6i2dMwBBcGQ8LJ7x0abzys4A==" - }, - "node_modules/@oslojs/asn1": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@oslojs/asn1/-/asn1-1.0.0.tgz", - "integrity": "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==", + "node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@oslojs/binary": "1.0.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@oslojs/binary": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@oslojs/binary/-/binary-1.0.0.tgz", - "integrity": "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@oslojs/crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@oslojs/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==", + "node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@oslojs/asn1": "1.0.0", - "@oslojs/binary": "1.0.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@oslojs/encoding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", - "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@oslojs/jwt": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@oslojs/jwt/-/jwt-0.2.0.tgz", - "integrity": "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg==", + "node_modules/arctic": { + "version": "2.3.4", "license": "MIT", "peer": true, "dependencies": { - "@oslojs/encoding": "0.4.1" + "@oslojs/crypto": "1.0.1", + "@oslojs/encoding": "1.1.0", + "@oslojs/jwt": "0.2.0" } }, - "node_modules/@oslojs/jwt/node_modules/@oslojs/encoding": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-0.4.1.tgz", - "integrity": "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q==", - "license": "MIT", - "peer": true - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/assertion-error": { + "version": "2.0.1", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=14" + "node": ">=12" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", - "cpu": [ - "x64" - ], + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.11", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/aws4fetch": { + "version": "1.0.20", + "license": "MIT" }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", - "cpu": [ - "arm64" - ], + "node_modules/balanced-match": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", - "cpu": [ - "arm64" - ], + "node_modules/brace-expansion": { + "version": "2.0.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", - "cpu": [ - "ia32" - ], + "node_modules/cac": { + "version": "6.7.14", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", - "cpu": [ - "x64" - ], + "node_modules/chai": { + "version": "5.3.3", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", - "cpu": [ - "x64" - ], + "node_modules/check-error": { + "version": "2.1.3", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0-beta.3.tgz", - "integrity": "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw==", - "license": "MIT" + "engines": { + "node": ">= 16" + } }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "node_modules/color-convert": { + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "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==", + "node_modules/color-name": { + "version": "1.1.4", "dev": true, "license": "MIT" }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/cross-spawn": { + "version": "7.0.6", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "node_modules/debug": { + "version": "4.4.3", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@types/proper-lockfile": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@types/proper-lockfile/-/proper-lockfile-4.1.4.tgz", - "integrity": "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==", + "node_modules/deep-eql": { + "version": "5.0.2", "dev": true, "license": "MIT", - "dependencies": { - "@types/retry": "*" + "engines": { + "node": ">=6" } }, - "node_modules/@types/retry": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", - "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", + "node_modules/eastasianwidth": { + "version": "0.2.0", "dev": true, "license": "MIT" }, - "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "bin": { + "esbuild": "bin/esbuild" }, - "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": 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==", + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/ui": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz", - "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.4", - "fflate": "^0.8.2", - "flatted": "^3.3.3", - "pathe": "^2.0.3", - "sirv": "^3.0.1", - "tinyglobby": "^0.2.14", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "3.2.4" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=18" } }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/arctic": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/arctic/-/arctic-2.3.4.tgz", - "integrity": "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA==", + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@oslojs/crypto": "1.0.1", - "@oslojs/encoding": "1.1.0", - "@oslojs/jwt": "0.2.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", - "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/aws4fetch": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz", - "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "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==", + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "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==", + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16" + "node": ">=18" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=7.0.0" + "node": ">=18" } }, - "node_modules/color-name": { - "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" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/es-module-lexer": { - "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==", + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" } }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1516,8 +1110,6 @@ }, "node_modules/expect-type": { "version": "1.3.0", - "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": { @@ -1526,8 +1118,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1542,24 +1132,26 @@ } } }, + "node_modules/fetch-socks": { + "version": "1.3.2", + "license": "MIT", + "dependencies": { + "socks": "^2.8.2", + "undici": ">=6" + } + }, "node_modules/fflate": { "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true, "license": "MIT" }, "node_modules/flatted": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -1573,25 +1165,19 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "ISC", "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -1611,14 +1197,10 @@ }, "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==", "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -1626,9 +1208,7 @@ } }, "node_modules/hono": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", - "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", + "version": "4.11.9", "license": "MIT", "peer": true, "engines": { @@ -1637,15 +1217,18 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, + "node_modules/ip-address": { + "version": "10.1.0", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "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": { @@ -1654,15 +1237,11 @@ }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1671,8 +1250,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1686,8 +1263,6 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1701,8 +1276,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1715,8 +1288,6 @@ }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -1731,38 +1302,28 @@ }, "node_modules/jose": { "version": "5.9.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", - "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } }, "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "10.0.0", "dev": true, "license": "MIT" }, "node_modules/loupe": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "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": { @@ -1771,8 +1332,6 @@ }, "node_modules/magicast": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1783,8 +1342,6 @@ }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -1799,8 +1356,6 @@ }, "node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -1815,8 +1370,6 @@ }, "node_modules/minipass": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { @@ -1825,8 +1378,6 @@ }, "node_modules/mrmime": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", "engines": { @@ -1835,202 +1386,508 @@ }, "node_modules/ms": { "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" }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" ], + "dev": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "BlueOak-1.0.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "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==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 14.16" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" ], + "dev": true, "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "node_modules/rollup/node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" + "node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "node_modules/rollup/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "node_modules/rollup/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", - "fsevents": "~2.3.2" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", "dev": true, "license": "ISC", "bin": { @@ -2042,8 +1899,6 @@ }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -2055,8 +1910,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -2065,28 +1918,15 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "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" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "version": "3.0.7", + "license": "ISC" }, "node_modules/sirv": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, "license": "MIT", "dependencies": { @@ -2098,10 +1938,28 @@ "node": ">=18" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", - "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": { @@ -2110,22 +1968,16 @@ }, "node_modules/stackback": { "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" }, "node_modules/std-env": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, "node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -2143,8 +1995,6 @@ "node_modules/string-width-cjs": { "name": "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": { @@ -2158,8 +2008,6 @@ }, "node_modules/string-width-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2168,15 +2016,11 @@ }, "node_modules/string-width-cjs/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" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2188,8 +2032,6 @@ }, "node_modules/strip-ansi": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -2205,8 +2047,6 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2218,8 +2058,6 @@ }, "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2228,8 +2066,6 @@ }, "node_modules/strip-literal": { "version": "3.1.0", - "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": { @@ -2239,10 +2075,13 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", - "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": { @@ -2254,8 +2093,6 @@ }, "node_modules/test-exclude": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "license": "ISC", "dependencies": { @@ -2269,22 +2106,16 @@ }, "node_modules/tinybench": { "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" }, "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" }, "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": { @@ -2300,8 +2131,6 @@ }, "node_modules/tinypool": { "version": "1.1.1", - "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": { @@ -2310,8 +2139,6 @@ }, "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -2320,8 +2147,6 @@ }, "node_modules/tinyspy": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", "engines": { @@ -2330,8 +2155,6 @@ }, "node_modules/totalist": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "license": "MIT", "engines": { @@ -2340,8 +2163,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2352,21 +2173,24 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.22.0", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, "node_modules/vite": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", - "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "version": "7.3.1", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -2436,8 +2260,6 @@ }, "node_modules/vite-node": { "version": "3.2.4", - "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": { @@ -2457,10 +2279,23 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/vitest": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", "dependencies": { @@ -2532,8 +2367,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -2548,8 +2381,6 @@ }, "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": { @@ -2565,8 +2396,6 @@ }, "node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2584,8 +2413,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2602,8 +2429,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2612,8 +2437,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -2628,15 +2451,11 @@ }, "node_modules/wrap-ansi-cjs/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" }, "node_modules/wrap-ansi-cjs/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": { @@ -2650,8 +2469,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2663,8 +2480,6 @@ }, "node_modules/xdg-basedir": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", "license": "MIT", "engines": { "node": ">=12" @@ -2674,9 +2489,7 @@ } }, "node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "version": "4.3.6", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -2684,8 +2497,6 @@ }, "node_modules/zod-to-json-schema": { "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "dev": true, "license": "ISC", "peerDependencies": { diff --git a/package.json b/package.json index 5a29971..26ae541 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,9 @@ "dependencies": { "@opencode-ai/plugin": "^0.15.30", "@openauthjs/openauth": "^0.4.3", + "fetch-socks": "^1.3.0", "proper-lockfile": "^4.1.2", + "undici": "^7.0.0", "xdg-basedir": "^5.1.0", "zod": "^4.0.0" } diff --git a/script/test-models.ts b/script/test-models.ts index 19af2f0..27d344a 100644 --- a/script/test-models.ts +++ b/script/test-models.ts @@ -33,6 +33,7 @@ const MODELS: ModelTest[] = [ const TEST_PROMPT = "Reply with exactly one word: WORKING"; const DEFAULT_TIMEOUT_MS = 120_000; +const OPENCODE_BIN = process.platform === "win32" ? "opencode.cmd" : "opencode"; interface TestResult { success: boolean; @@ -44,8 +45,9 @@ async function testModel(model: string, timeoutMs: number): Promise const start = Date.now(); return new Promise((resolve) => { - const proc = spawn("opencode", ["run", TEST_PROMPT, "--model", model], { + const proc = spawn(OPENCODE_BIN, ["run", TEST_PROMPT, "--model", model], { stdio: ["ignore", "pipe", "pipe"], + shell: process.platform === "win32", }); let stdout = ""; diff --git a/script/test-regression.ts b/script/test-regression.ts index 1792ca1..6cffa00 100644 --- a/script/test-regression.ts +++ b/script/test-regression.ts @@ -56,6 +56,7 @@ const GEMINI_FLASH = "google/antigravity-gemini-3-flash"; const GEMINI_FLASH_CLI_QUOTA = "google/gemini-2.5-flash"; const CLAUDE_SONNET = "google/antigravity-claude-sonnet-4-5-thinking-low"; const CLAUDE_OPUS = "google/antigravity-claude-opus-4-5-thinking-low"; +const OPENCODE_BIN = process.platform === "win32" ? "opencode.cmd" : "opencode"; const SANITY_TESTS: MultiTurnTest[] = [ { @@ -291,8 +292,9 @@ async function runTurn( ? ["run", prompt, "--session", sessionId, "--model", model] : ["run", prompt, "--model", model, "--title", sessionTitle]; - const proc = spawn("opencode", args, { + const proc = spawn(OPENCODE_BIN, args, { stdio: ["ignore", "pipe", "pipe"], + shell: process.platform === "win32", cwd: process.cwd(), }); @@ -345,8 +347,9 @@ async function runTurn( async function deleteSession(sessionId: string): Promise { return new Promise((resolve) => { - const proc = spawn("opencode", ["session", "delete", sessionId, "--force"], { + const proc = spawn(OPENCODE_BIN, ["session", "delete", sessionId, "--force"], { stdio: ["ignore", "pipe", "pipe"], + shell: process.platform === "win32", timeout: 10000, cwd: process.cwd(), }); diff --git a/src/antigravity/oauth.test.ts b/src/antigravity/oauth.test.ts new file mode 100644 index 0000000..a30e23c --- /dev/null +++ b/src/antigravity/oauth.test.ts @@ -0,0 +1,107 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" + +const { fetchWithProxyMock } = vi.hoisted(() => ({ + fetchWithProxyMock: vi.fn(), +})) + +vi.mock("../plugin/proxy", () => ({ + fetchWithProxy: fetchWithProxyMock, +})) + +import { exchangeAntigravity } from "./oauth" + +function makeState(verifier: string, projectId = ""): string { + return Buffer.from(JSON.stringify({ verifier, projectId }), "utf8").toString("base64url") +} + +describe("exchangeAntigravity proxy routing", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("routes token and userinfo requests through account-scoped proxy args", async () => { + fetchWithProxyMock + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + access_token: "access-1", + expires_in: 3600, + refresh_token: "refresh-1", + }), + { status: 200 }, + ), + ) + .mockResolvedValueOnce( + new Response( + JSON.stringify({ email: "user@example.com" }), + { status: 200 }, + ), + ) + + const proxies = [{ url: "socks5://127.0.0.1:1080" }] + const accountIndex = 4 + + const result = await exchangeAntigravity( + "auth-code", + makeState("pkce-verifier", "project-1"), + proxies, + accountIndex, + ) + + expect(result.type).toBe("success") + expect(fetchWithProxyMock).toHaveBeenCalledTimes(2) + + const firstCall = fetchWithProxyMock.mock.calls[0] ?? [] + const secondCall = fetchWithProxyMock.mock.calls[1] ?? [] + + expect(firstCall[0]).toBe("https://oauth2.googleapis.com/token") + expect(secondCall[0]).toBe("https://www.googleapis.com/oauth2/v1/userinfo?alt=json") + + expect(firstCall[2]).toEqual(proxies) + expect(firstCall[3]).toBe(accountIndex) + expect(secondCall[2]).toEqual(proxies) + expect(secondCall[3]).toBe(accountIndex) + }) + + it("routes project discovery request through account-scoped proxy args when projectId is missing", async () => { + fetchWithProxyMock + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + access_token: "access-2", + expires_in: 3600, + refresh_token: "refresh-2", + }), + { status: 200 }, + ), + ) + .mockResolvedValueOnce(new Response("{}", { status: 500 })) + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + cloudaicompanionProject: "resolved-project", + }), + { status: 200 }, + ), + ) + + const proxies = [{ url: "http://127.0.0.1:8080" }] + const accountIndex = 9 + + const result = await exchangeAntigravity( + "auth-code", + makeState("pkce-verifier"), + proxies, + accountIndex, + ) + + expect(result.type).toBe("success") + expect(fetchWithProxyMock).toHaveBeenCalledTimes(3) + + const projectCall = fetchWithProxyMock.mock.calls[2] ?? [] + expect(typeof projectCall[0]).toBe("string") + expect(projectCall[0]).toContain("/v1internal:loadCodeAssist") + expect(projectCall[2]).toEqual(proxies) + expect(projectCall[3]).toBe(accountIndex) + }) +}) \ No newline at end of file diff --git a/src/antigravity/oauth.ts b/src/antigravity/oauth.ts index 66eea22..0055566 100644 --- a/src/antigravity/oauth.ts +++ b/src/antigravity/oauth.ts @@ -12,6 +12,8 @@ import { } from "../constants"; import { createLogger } from "../plugin/logger"; import { calculateTokenExpiry } from "../plugin/auth"; +import { fetchWithProxy } from "../plugin/proxy"; +import type { ProxyConfig } from "../plugin/storage"; const log = createLogger("oauth"); @@ -119,17 +121,19 @@ async function fetchWithTimeout( url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); try { - return await fetch(url, { ...options, signal: controller.signal }); + return await fetchWithProxy(url, { ...options, signal: controller.signal }, proxies, accountIndex); } finally { clearTimeout(timeout); } } -async function fetchProjectID(accessToken: string): Promise { +async function fetchProjectID(accessToken: string, proxies?: ProxyConfig[], accountIndex?: number): Promise { const errors: string[] = []; const loadHeaders: Record = { Authorization: `Bearer ${accessToken}`, @@ -155,7 +159,7 @@ async function fetchProjectID(accessToken: string): Promise { pluginType: "GEMINI", }, }), - }); + }, FETCH_TIMEOUT_MS, proxies, accountIndex); if (!response.ok) { const message = await response.text().catch(() => ""); @@ -201,28 +205,36 @@ async function fetchProjectID(accessToken: string): Promise { export async function exchangeAntigravity( code: string, state: string, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { try { const { verifier, projectId } = decodeState(state); const startTime = Date.now(); - const tokenResponse = await fetch("https://oauth2.googleapis.com/token", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate, br", - "User-Agent": GEMINI_CLI_HEADERS["User-Agent"], + const tokenResponse = await fetchWithTimeout( + "https://oauth2.googleapis.com/token", + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate, br", + "User-Agent": GEMINI_CLI_HEADERS["User-Agent"], + }, + body: new URLSearchParams({ + client_id: ANTIGRAVITY_CLIENT_ID, + client_secret: ANTIGRAVITY_CLIENT_SECRET, + code, + grant_type: "authorization_code", + redirect_uri: ANTIGRAVITY_REDIRECT_URI, + code_verifier: verifier, + }), }, - body: new URLSearchParams({ - client_id: ANTIGRAVITY_CLIENT_ID, - client_secret: ANTIGRAVITY_CLIENT_SECRET, - code, - grant_type: "authorization_code", - redirect_uri: ANTIGRAVITY_REDIRECT_URI, - code_verifier: verifier, - }), - }); + FETCH_TIMEOUT_MS, + proxies, + accountIndex, + ); if (!tokenResponse.ok) { const errorText = await tokenResponse.text(); @@ -231,7 +243,7 @@ export async function exchangeAntigravity( const tokenPayload = (await tokenResponse.json()) as AntigravityTokenResponse; - const userInfoResponse = await fetch( + const userInfoResponse = await fetchWithTimeout( "https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { @@ -239,6 +251,9 @@ export async function exchangeAntigravity( "User-Agent": GEMINI_CLI_HEADERS["User-Agent"], }, }, + FETCH_TIMEOUT_MS, + proxies, + accountIndex, ); const userInfo = userInfoResponse.ok @@ -252,7 +267,7 @@ export async function exchangeAntigravity( let effectiveProjectId = projectId; if (!effectiveProjectId) { - effectiveProjectId = await fetchProjectID(tokenPayload.access_token); + effectiveProjectId = await fetchProjectID(tokenPayload.access_token, proxies, accountIndex); } const storedRefresh = `${refreshToken}|${effectiveProjectId || ""}`; diff --git a/src/plugin.ts b/src/plugin.ts index cad8209..675c248 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -11,8 +11,16 @@ import { import { authorizeAntigravity, exchangeAntigravity } from "./antigravity/oauth"; import type { AntigravityTokenExchangeResult } from "./antigravity/oauth"; import { accessTokenExpired, isOAuthAuth, parseRefreshParts, formatRefreshParts } from "./plugin/auth"; -import { promptAddAnotherAccount, promptLoginMode, promptProjectId } from "./plugin/cli"; +import { + promptAccountProxyConfiguration, + promptAddAnotherAccount, + isTTY, + promptLoginMode, + promptOAuthProxyConfiguration, + promptProjectId, +} from "./plugin/cli"; import { ensureProjectContext } from "./plugin/project"; +import { fetchWithProxy, ProxyExhaustedError } from "./plugin/proxy"; import { startAntigravityDebugRequest, logAntigravityDebugResponse, @@ -39,7 +47,13 @@ import { import { EmptyResponseError } from "./plugin/errors"; import { AntigravityTokenRefreshError, refreshAccessToken } from "./plugin/token"; import { startOAuthListener, type OAuthListener } from "./plugin/server"; -import { clearAccounts, loadAccounts, saveAccounts, saveAccountsReplace } from "./plugin/storage"; +import { + clearAccounts, + loadAccounts, + saveAccounts, + saveAccountsReplace, + type ProxyConfig, +} from "./plugin/storage"; import { AccountManager, type ModelFamily, parseRateLimitReason, calculateBackoffMs, computeSoftQuotaCacheTtlMs } from "./plugin/accounts"; import { createAutoUpdateCheckerHook } from "./hooks/auto-update-checker"; import { loadConfig, initRuntimeConfig, type AntigravityConfig } from "./plugin/config"; @@ -155,7 +169,7 @@ async function triggerAsyncQuotaRefreshForAccount( return; } - const results = await checkAccountsQuota([singleAccount], client, providerId); + const results = await checkAccountsQuota([singleAccount], client, providerId, [accountIndex]); if (results[0]?.status === "ok" && results[0]?.quota?.groups) { accountManager.updateQuotaCache(accountIndex, results[0].quota.groups); @@ -441,6 +455,8 @@ async function verifyAccountAccess( email?: string; projectId?: string; managedProjectId?: string; + proxies?: ProxyConfig[]; + accountIndex?: number; }, client: PluginClient, providerId: string, @@ -463,7 +479,7 @@ async function verifyAccountAccess( let refreshedAuth: Awaited>; try { - refreshedAuth = await refreshAccessToken(auth, client, providerId); + refreshedAuth = await refreshAccessToken(auth, client, providerId, account.proxies, account.accountIndex); } catch (error) { if (error instanceof AntigravityTokenRefreshError) { return { status: "error", message: error.message }; @@ -505,12 +521,17 @@ async function verifyAccountAccess( let response: Response; try { - response = await fetch(`${ANTIGRAVITY_ENDPOINT_PROD}/v1internal:streamGenerateContent?alt=sse`, { - method: "POST", - headers, - body: JSON.stringify(requestBody), - signal: controller.signal, - }); + response = await fetchWithProxy( + `${ANTIGRAVITY_ENDPOINT_PROD}/v1internal:streamGenerateContent?alt=sse`, + { + method: "POST", + headers, + body: JSON.stringify(requestBody), + signal: controller.signal, + }, + account.proxies, + account.accountIndex, + ); } catch (error) { if (error instanceof Error && error.name === "AbortError") { return { status: "error", message: "Verification check timed out." }; @@ -730,6 +751,8 @@ function parseOAuthCallbackInput( async function promptManualOAuthInput( fallbackState: string, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { console.log("1. Open the URL above in your browser and complete Google sign-in."); console.log("2. After approving, copy the full redirected localhost URL from the address bar."); @@ -743,7 +766,7 @@ async function promptManualOAuthInput( return { type: "failed", error: params.error }; } - return exchangeAntigravity(params.code, params.state); + return exchangeAntigravity(params.code, params.state, proxies, accountIndex); } function clampInt(value: number, min: number, max: number): number { @@ -756,6 +779,7 @@ function clampInt(value: number, min: number, max: number): number { async function persistAccountPool( results: Array>, replaceAll: boolean = false, + resultProxies: Array = [], ): Promise { if (results.length === 0) { return; @@ -780,7 +804,8 @@ async function persistAccountPool( } } - for (const result of results) { + for (const [resultIndex, result] of results.entries()) { + const configuredProxies = resultProxies[resultIndex]; const parts = parseRefreshParts(result.refresh); if (!parts.refreshToken) { continue; @@ -809,6 +834,7 @@ async function persistAccountPool( addedAt: now, lastUsed: now, enabled: true, + proxies: configuredProxies !== undefined ? [...configuredProxies] : [], }); continue; } @@ -828,6 +854,7 @@ async function persistAccountPool( projectId: parts.projectId ?? existing.projectId, managedProjectId: parts.managedProjectId ?? existing.managedProjectId, lastUsed: now, + proxies: configuredProxies !== undefined ? [...configuredProxies] : existing.proxies ?? [], }; // Update the token index if the token changed @@ -1352,11 +1379,17 @@ export const createAntigravityPlugin = (providerId: string) => async ( const parts = parseRefreshParts(auth.refresh); const projectId = parts.managedProjectId || parts.projectId || "unknown"; + const searchAccount = activeAccountManager?.getAccounts().find((acc) => { + return acc.parts.refreshToken === parts.refreshToken; + }); + const searchProxies = searchAccount?.proxies; + const searchAccountIndex = searchAccount?.index; + // Ensure we have a valid access token let accessToken = auth.access; if (!accessToken || accessTokenExpired(auth)) { try { - const refreshed = await refreshAccessToken(auth, client, providerId); + const refreshed = await refreshAccessToken(auth, client, providerId, searchProxies, searchAccountIndex); accessToken = refreshed?.access; } catch (error) { return `Error: Failed to refresh access token: ${error instanceof Error ? error.message : String(error)}`; @@ -1376,6 +1409,8 @@ export const createAntigravityPlugin = (providerId: string) => async ( accessToken, projectId, ctx.abort, + searchProxies, + searchAccountIndex, ); }, }); @@ -1705,7 +1740,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( if (accessTokenExpired(authRecord)) { try { - const refreshed = await refreshAccessToken(authRecord, client, providerId); + const refreshed = await refreshAccessToken(authRecord, client, providerId, account.proxies, account.index); if (!refreshed) { const { failures, shouldCooldown, cooldownMs } = trackAccountFailure(account.index); getHealthTracker().recordFailure(account.index); @@ -1779,7 +1814,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( let projectContext: ProjectContextResult; try { - projectContext = await ensureProjectContext(authRecord); + projectContext = await ensureProjectContext(authRecord, account.proxies, account.index); resetAccountFailureState(account.index); } catch (error) { const { failures, shouldCooldown, cooldownMs } = trackAccountFailure(account.index); @@ -1847,7 +1882,12 @@ export const createAntigravityPlugin = (providerId: string) => async ( try { pushDebug("thinking-warmup: start"); - const warmupResponse = await fetch(warmupUrl, warmupInit); + const warmupResponse = await fetchWithProxy( + warmupUrl, + warmupInit, + account.proxies, + account.index, + ); const transformed = await transformAntigravityResponse( warmupResponse, true, @@ -2007,7 +2047,12 @@ export const createAntigravityPlugin = (providerId: string) => async ( tokenConsumed = getTokenTracker().consume(account.index); } - const response = await fetch(prepared.request, prepared.init); + const response = await fetchWithProxy( + prepared.request, + prepared.init, + account.proxies, + account.index, + ); pushDebug(`status=${response.status} ${response.statusText}`); @@ -2441,6 +2486,13 @@ export const createAntigravityPlugin = (providerId: string) => async ( tokenConsumed = false; } + if (error instanceof ProxyExhaustedError && i === ANTIGRAVITY_ENDPOINT_FALLBACKS.length - 1) { + const proxyErrorMessage = error.proxyCount > 0 + ? `All proxies failed for ${account.email || `Account ${account.index + 1}`}. Check proxy configuration.` + : `No enabled proxy configured for ${account.email || `Account ${account.index + 1}`}. Refusing direct connection to avoid IP leakage.` + await showToast(proxyErrorMessage, "error") + } + // Handle recoverable thinking errors - retry with forced recovery if (error instanceof Error && error.message === "THINKING_RECOVERY_NEEDED") { // Only retry once with forced recovery to avoid infinite loops @@ -2452,10 +2504,18 @@ export const createAntigravityPlugin = (providerId: string) => async ( } // Already tried with forced recovery, give up and return error - const recoveryError = error as any; - const originalError = recoveryError.originalError || { error: { message: "Thinking recovery triggered" } }; - - const recoveryMessage = `${originalError.error?.message || "Session recovery failed"}\n\n[RECOVERY] Thinking block corruption could not be resolved. Try starting a new session.`; + const originalError = ( + typeof error === "object" && + error !== null && + "originalError" in error && + typeof (error as { originalError?: unknown }).originalError === "object" && + (error as { originalError?: unknown }).originalError !== null + ) + ? (error as { originalError: { error?: { message?: string } } }).originalError + : undefined; + const originalMessage = originalError?.error?.message || "Thinking recovery triggered"; + + const recoveryMessage = `${originalMessage || "Session recovery failed"}\n\n[RECOVERY] Thinking block corruption could not be resolved. Try starting a new session.`; return new Response(JSON.stringify({ type: "error", @@ -2734,6 +2794,33 @@ export const createAntigravityPlugin = (providerId: string) => async ( } if (menuResult.mode === "manage") { + if (menuResult.configureProxyAccountIndex !== undefined) { + const proxyAccountIndex = menuResult.configureProxyAccountIndex; + const acc = existingStorage.accounts[proxyAccountIndex]; + if (!acc) { + console.log(`\nAccount ${proxyAccountIndex + 1} not found.\n`); + continue; + } + + const label = acc.email || `Account ${proxyAccountIndex + 1}`; + const updatedProxies = await promptAccountProxyConfiguration(label, acc.proxies ?? []); + if (updatedProxies === undefined) { + console.log("\nProxy configuration cancelled.\n"); + continue; + } + + acc.proxies = [...updatedProxies]; + await saveAccounts(existingStorage); + activeAccountManager?.setAccountProxies(proxyAccountIndex, updatedProxies); + + if (updatedProxies.length === 0) { + console.log(`\nCleared proxies for ${label}.\n`); + } else { + const enabledCount = updatedProxies.filter((proxy) => proxy.enabled !== false).length; + console.log(`\nUpdated proxies for ${label}: ${updatedProxies.length} configured (${enabledCount} enabled).\n`); + } + } + if (menuResult.toggleAccountIndex !== undefined) { const acc = existingStorage.accounts[menuResult.toggleAccountIndex]; if (acc) { @@ -2771,7 +2858,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( const label = account.email || `Account ${i + 1}`; process.stdout.write(`- [${i + 1}/${existingStorage.accounts.length}] ${label} ... `); - const verification = await verifyAccountAccess(account, client, providerId); + const verification = await verifyAccountAccess({ ...account, accountIndex: i }, client, providerId); if (verification.status === "ok") { const { changed, wasVerificationRequired } = clearStoredAccountVerificationRequired(account, true); if (changed) { @@ -2853,7 +2940,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( const label = account.email || `Account ${verifyAccountIndex + 1}`; console.log(`\nChecking verification status for ${label}...\n`); - const verification = await verifyAccountAccess(account, client, providerId); + const verification = await verifyAccountAccess({ ...account, accountIndex: verifyAccountIndex }, client, providerId); if (verification.status === "ok") { const { changed, wasVerificationRequired } = clearStoredAccountVerificationRequired(account, true); @@ -3014,7 +3101,22 @@ export const createAntigravityPlugin = (providerId: string) => async ( while (accounts.length < MAX_OAUTH_ACCOUNTS) { console.log(`\n=== Antigravity OAuth (Account ${accounts.length + 1}) ===`); + const defaultProxies = + refreshAccountIndex !== undefined + ? existingStorage?.accounts[refreshAccountIndex]?.proxies ?? [] + : []; + const proxies = await promptOAuthProxyConfiguration(defaultProxies); + if (proxies.length > 0) { + console.log("\nProxy configured for this account."); + console.log("Ensure your browser/system login environment is also routed through the same proxy to avoid IP leakage during Google sign-in.\n"); + } + const projectId = await promptProjectId(); + const accountIndexBase = startFresh ? 0 : (existingStorage?.accounts.length ?? 0); + const accountIndex = + refreshAccountIndex !== undefined + ? refreshAccountIndex + : accountIndexBase + accounts.length; const result = await (async (): Promise => { const authorization = await authorizeAntigravity(projectId); @@ -3028,7 +3130,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( console.log("Could not open browser automatically."); console.log("Please open the URL above manually in your local browser.\n"); } - return promptManualOAuthInput(fallbackState); + return promptManualOAuthInput(fallbackState, proxies, accountIndex); } let listener: OAuthListener | null = null; @@ -3066,7 +3168,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( await listener.close(); } catch {} - return promptManualOAuthInput(fallbackState); + return promptManualOAuthInput(fallbackState, proxies, accountIndex); } throw err; } @@ -3076,7 +3178,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( return { type: "failed", error: "Missing code or state in callback URL" }; } - return exchangeAntigravity(params.code, params.state); + return exchangeAntigravity(params.code, params.state, proxies, accountIndex); } catch (error) { if (error instanceof Error && error.message !== "SOFT_TIMEOUT") { return { @@ -3095,7 +3197,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( } } - return promptManualOAuthInput(fallbackState); + return promptManualOAuthInput(fallbackState, proxies, accountIndex); })(); if (result.type === "failed") { @@ -3129,29 +3231,42 @@ export const createAntigravityPlugin = (providerId: string) => async ( try { if (refreshAccountIndex !== undefined) { const currentStorage = await loadAccounts(); - if (currentStorage) { + const parts = parseRefreshParts(result.refresh); + let refreshedInPlace = false; + + if (currentStorage && parts.refreshToken) { const updatedAccounts = [...currentStorage.accounts]; - const parts = parseRefreshParts(result.refresh); - if (parts.refreshToken) { + const currentAccount = updatedAccounts[refreshAccountIndex]; + + if (currentAccount) { updatedAccounts[refreshAccountIndex] = { - email: result.email ?? updatedAccounts[refreshAccountIndex]?.email, + ...currentAccount, + email: result.email ?? currentAccount.email, refreshToken: parts.refreshToken, - projectId: parts.projectId ?? updatedAccounts[refreshAccountIndex]?.projectId, - managedProjectId: parts.managedProjectId ?? updatedAccounts[refreshAccountIndex]?.managedProjectId, - addedAt: updatedAccounts[refreshAccountIndex]?.addedAt ?? Date.now(), + projectId: parts.projectId ?? currentAccount.projectId, + managedProjectId: parts.managedProjectId ?? currentAccount.managedProjectId, + addedAt: currentAccount.addedAt ?? Date.now(), lastUsed: Date.now(), + proxies: [...proxies], }; + await saveAccounts({ version: 4, accounts: updatedAccounts, activeIndex: currentStorage.activeIndex, activeIndexByFamily: currentStorage.activeIndexByFamily, }); + activeAccountManager?.setAccountProxies(refreshAccountIndex, proxies); + refreshedInPlace = true; } } + + if (!refreshedInPlace) { + await persistAccountPool([result], false, [proxies]); + } } else { const isFirstAccount = accounts.length === 1; - await persistAccountPool([result], isFirstAccount && startFresh); + await persistAccountPool([result], isFirstAccount && startFresh, [proxies]); } } catch { } @@ -3180,7 +3295,6 @@ export const createAntigravityPlugin = (providerId: string) => async ( break; } } - const primary = accounts[0]; if (!primary) { return { @@ -3212,7 +3326,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( }; } - // TUI flow (`/connect`) does not support per-account prompts. + // TUI flow (`/connect`) does not support per-account selection prompts. // Default to adding new accounts (non-destructive). // Users can run `opencode auth logout` first if they want a fresh start. const projectId = ""; @@ -3220,9 +3334,19 @@ export const createAntigravityPlugin = (providerId: string) => async ( // Check existing accounts count for toast message const existingStorage = await loadAccounts(); const existingCount = existingStorage?.accounts.length ?? 0; + const defaultProxyAccount = existingStorage?.accounts[existingStorage?.activeIndex ?? 0]; + const defaultProxies = defaultProxyAccount?.proxies ?? []; + const proxies = isTTY() ? await promptOAuthProxyConfiguration(defaultProxies) : []; + if (proxies.length > 0) { + console.log("\nProxy configured for this OAuth flow."); + console.log("Ensure your browser/system login environment is also routed through the same proxy to avoid IP leakage during Google sign-in.\n"); + } + const accountIndex = existingCount; + const proxyReminder = proxies.length > 0 + ? "\nProxy is enabled. Ensure your browser/system login environment also uses the same proxy to avoid IP leakage." + : ""; const useManualFlow = isHeadless || shouldSkipLocalServer(); - let listener: OAuthListener | null = null; if (!useManualFlow) { try { @@ -3247,7 +3371,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( return { url: authorization.url, instructions: - "Complete sign-in in your browser. We'll automatically detect the redirect back to localhost.", + "Complete sign-in in your browser. We'll automatically detect the redirect back to localhost." + proxyReminder, method: "auto", callback: async (): Promise => { const CALLBACK_TIMEOUT_MS = 30000; @@ -3275,10 +3399,10 @@ export const createAntigravityPlugin = (providerId: string) => async ( return { type: "failed", error: "Missing code or state in callback URL" }; } - const result = await exchangeAntigravity(params.code, params.state); + const result = await exchangeAntigravity(params.code, params.state, proxies, accountIndex); if (result.type === "success") { try { - await persistAccountPool([result], false); + await persistAccountPool([result], false, [proxies]); } catch { } @@ -3317,7 +3441,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( return { url: authorization.url, instructions: - "Visit the URL above, complete OAuth, then paste either the full redirect URL or the authorization code.", + "Visit the URL above, complete OAuth, then paste either the full redirect URL or the authorization code." + proxyReminder, method: "code", callback: async (codeInput: string): Promise => { const params = parseOAuthCallbackInput(codeInput, fallbackState); @@ -3325,11 +3449,11 @@ export const createAntigravityPlugin = (providerId: string) => async ( return { type: "failed", error: params.error }; } - const result = await exchangeAntigravity(params.code, params.state); + const result = await exchangeAntigravity(params.code, params.state, proxies, accountIndex); if (result.type === "success") { try { // TUI flow adds to existing accounts (non-destructive) - await persistAccountPool([result], false); + await persistAccountPool([result], false, [proxies]); } catch { // ignore } diff --git a/src/plugin/accounts.test.ts b/src/plugin/accounts.test.ts index 24a02a5..434bfce 100644 --- a/src/plugin/accounts.test.ts +++ b/src/plugin/accounts.test.ts @@ -38,6 +38,24 @@ describe("AccountManager", () => { expect(manager.getAccountCount()).toBe(0); }); + + it("defaults missing proxies to [] and allows per-account proxy updates", () => { + const stored: AccountStorageV4 = { + version: 4, + accounts: [ + { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 }, + ], + activeIndex: 0, + }; + + const manager = new AccountManager(undefined, stored); + + expect(manager.getAccountsSnapshot()[0]?.proxies).toEqual([]); + + const updated = manager.setAccountProxies(0, [{ url: "http://127.0.0.1:8080" }]); + expect(updated).toBe(true); + expect(manager.getAccountsSnapshot()[0]?.proxies).toEqual([{ url: "http://127.0.0.1:8080" }]); + }); it("returns current account when not rate-limited for family", () => { const stored: AccountStorageV4 = { version: 4, diff --git a/src/plugin/accounts.ts b/src/plugin/accounts.ts index e86ed5f..2ce9d6b 100644 --- a/src/plugin/accounts.ts +++ b/src/plugin/accounts.ts @@ -152,6 +152,7 @@ export interface ManagedAccount { verificationRequiredAt?: number; verificationRequiredReason?: string; verificationUrl?: string; + proxies?: import("./storage").ProxyConfig[]; } function nowMs(): number { @@ -367,6 +368,7 @@ export class AccountManager { verificationRequiredAt: acc.verificationRequiredAt, verificationRequiredReason: acc.verificationRequiredReason, verificationUrl: acc.verificationUrl, + proxies: acc.proxies ?? [], }; }) .filter((a): a is ManagedAccount => a !== null); @@ -405,6 +407,7 @@ export class AccountManager { enabled: true, rateLimitResetTimes: {}, touchedForQuota: {}, + proxies: [], }; this.accounts.push(newAccount); // Update indices to include the new account @@ -429,6 +432,7 @@ export class AccountManager { enabled: true, rateLimitResetTimes: {}, touchedForQuota: {}, + proxies: [], }, ]; this.cursor = 0; @@ -806,6 +810,15 @@ export class AccountManager { this.requestSaveToDisk(); return true; } + setAccountProxies(accountIndex: number, proxies: import("./storage").ProxyConfig[]): boolean { + const account = this.accounts[accountIndex]; + if (!account) { + return false; + } + account.proxies = [...proxies]; + this.requestSaveToDisk(); + return true; + } markAccountVerificationRequired(accountIndex: number, reason?: string, verifyUrl?: string): boolean { const account = this.accounts[accountIndex]; @@ -999,6 +1012,7 @@ export class AccountManager { verificationRequiredAt: a.verificationRequiredAt, verificationRequiredReason: a.verificationRequiredReason, verificationUrl: a.verificationUrl, + proxies: a.proxies, })), activeIndex: claudeIndex, activeIndexByFamily: { @@ -1161,6 +1175,7 @@ export class AccountManager { addedAt: a.addedAt, lastUsed: a.lastUsed, enabled: a.enabled, + proxies: a.proxies, })); } diff --git a/src/plugin/cli.test.ts b/src/plugin/cli.test.ts new file mode 100644 index 0000000..7a84938 --- /dev/null +++ b/src/plugin/cli.test.ts @@ -0,0 +1,169 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => { + const state = { answers: [] as string[] }; + const question = vi.fn(async () => state.answers.shift() ?? ""); + const close = vi.fn(); + const createInterface = vi.fn(() => ({ question, close })); + + const showAuthMenu = vi.fn(); + const showAccountDetails = vi.fn(); + const isTTY = vi.fn(() => true); + const updateOpencodeConfig = vi.fn(async () => ({ success: true, configPath: "opencode.json" })); + + return { + state, + question, + close, + createInterface, + showAuthMenu, + showAccountDetails, + isTTY, + updateOpencodeConfig, + }; +}); + +vi.mock("node:readline/promises", () => ({ + createInterface: mocks.createInterface, +})); + +vi.mock("./ui/auth-menu", () => ({ + showAuthMenu: mocks.showAuthMenu, + showAccountDetails: mocks.showAccountDetails, + isTTY: mocks.isTTY, +})); + +vi.mock("./config/updater", () => ({ + updateOpencodeConfig: mocks.updateOpencodeConfig, +})); + +import { + promptAccountProxyConfiguration, + promptLoginMode, + promptOAuthProxyConfiguration, +} from "./cli"; + +describe("cli proxy behavior", () => { + let logSpy: ReturnType; + + beforeEach(() => { + logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + mocks.state.answers = []; + mocks.question.mockClear(); + mocks.close.mockClear(); + mocks.createInterface.mockClear(); + mocks.showAuthMenu.mockReset(); + mocks.showAccountDetails.mockReset(); + mocks.isTTY.mockReset(); + mocks.isTTY.mockReturnValue(true); + mocks.updateOpencodeConfig.mockReset(); + mocks.updateOpencodeConfig.mockResolvedValue({ success: true, configPath: "opencode.json" }); + }); + + afterEach(() => { + logSpy.mockRestore(); + }); + + it("reuses existing oauth proxies by default and returns a copy", async () => { + const existing = [{ url: "http://127.0.0.1:8080/" }]; + mocks.state.answers = [""]; + + const result = await promptOAuthProxyConfiguration(existing); + + expect(result).toEqual(existing); + expect(result).not.toBe(existing); + expect(mocks.close).toHaveBeenCalledTimes(1); + }); + + it("lets oauth proxy manager add and merge proxy entries", async () => { + mocks.state.answers = [ + "y", + "a", + "http://127.0.0.1:8080", + "a", + "!http://127.0.0.1:8080,socks5://127.0.0.1:1080", + "s", + ]; + + const result = await promptOAuthProxyConfiguration([]); + + expect(result).toEqual([ + { url: "http://127.0.0.1:8080/", enabled: false }, + { url: "socks5://127.0.0.1:1080" }, + ]); + }); + + it("re-prompts oauth proxy add when input is invalid", async () => { + mocks.state.answers = [ + "y", + "a", + "ftp://bad", + "a", + "http://127.0.0.1:8080", + "s", + ]; + + const result = await promptOAuthProxyConfiguration([]); + + expect(result).toEqual([{ url: "http://127.0.0.1:8080/" }]); + }); + + it("returns empty proxy list when oauth proxy setup is declined", async () => { + mocks.state.answers = ["n"]; + + const result = await promptOAuthProxyConfiguration([]); + + expect(result).toEqual([]); + }); + + it("keeps account proxy config unchanged when user cancels", async () => { + mocks.state.answers = ["x"]; + + const result = await promptAccountProxyConfiguration("Account 1", [{ url: "http://127.0.0.1:8080/" }]); + + expect(result).toBeUndefined(); + }); + + it("deletes a single selected proxy from account manager", async () => { + mocks.state.answers = ["d", "1", "s"]; + + const result = await promptAccountProxyConfiguration("Account 1", [ + { url: "http://127.0.0.1:8080/" }, + { url: "socks5://127.0.0.1:1080" }, + ]); + + expect(result).toEqual([{ url: "socks5://127.0.0.1:1080" }]); + }); + + it("toggles a single selected proxy in account manager", async () => { + mocks.state.answers = ["t", "1", "s"]; + + const result = await promptAccountProxyConfiguration("Account 2", [{ url: "http://127.0.0.1:8080/" }]); + + expect(result).toEqual([{ url: "http://127.0.0.1:8080/", enabled: false }]); + }); + + it("returns configureProxyAccountIndex when account proxy action is selected", async () => { + const account = { index: 3, email: "a@example.com" }; + mocks.showAuthMenu.mockResolvedValue({ type: "select-account", account }); + mocks.showAccountDetails.mockResolvedValue("proxy"); + + const result = await promptLoginMode([account]); + + expect(result).toEqual({ mode: "manage", configureProxyAccountIndex: 3 }); + }); + + it("supports proxy management selection in non-tty fallback", async () => { + mocks.isTTY.mockReturnValue(false); + mocks.state.answers = ["p", "2"]; + + const result = await promptLoginMode([ + { index: 0, email: "first@example.com" }, + { index: 1, email: "second@example.com" }, + ]); + + expect(result).toEqual({ mode: "manage", configureProxyAccountIndex: 1 }); + }); +}); + diff --git a/src/plugin/cli.ts b/src/plugin/cli.ts index 348c180..675526e 100644 --- a/src/plugin/cli.ts +++ b/src/plugin/cli.ts @@ -8,6 +8,218 @@ import { type AccountStatus, } from "./ui/auth-menu"; import { updateOpencodeConfig } from "./config/updater"; +import type { ProxyConfig } from "./storage"; + +const SUPPORTED_PROXY_PROTOCOLS = new Set([ + "http:", + "https:", + "socks4:", + "socks4a:", + "socks5:", + "socks5h:", +]); + +function normalizeProxyUrl(value: string): string | null { + try { + const url = new URL(value); + if (!SUPPORTED_PROXY_PROTOCOLS.has(url.protocol.toLowerCase())) { + return null; + } + return url.toString(); + } catch { + return null; + } +} + +function parseProxyInput(value: string): { + proxies: ProxyConfig[]; + invalid: string[]; +} { + const tokens = value + .split(",") + .map((token) => token.trim()) + .filter(Boolean); + + const invalid: string[] = []; + const dedup = new Map(); + + for (const rawToken of tokens) { + const disabled = rawToken.startsWith("!"); + const token = disabled ? rawToken.slice(1).trim() : rawToken; + const normalizedUrl = normalizeProxyUrl(token); + if (!normalizedUrl) { + invalid.push(rawToken); + continue; + } + + const proxy: ProxyConfig = disabled + ? { url: normalizedUrl, enabled: false } + : { url: normalizedUrl }; + dedup.set(proxy.url, proxy); + } + + return { + proxies: [...dedup.values()], + invalid, + }; +} + +function cloneProxies(proxies: ProxyConfig[]): ProxyConfig[] { + return proxies.map((proxy) => ({ ...proxy })); +} + +function mergeProxyConfigs(existing: ProxyConfig[], additions: ProxyConfig[]): ProxyConfig[] { + const dedup = new Map(); + + for (const proxy of existing) { + dedup.set(proxy.url, { ...proxy }); + } + + for (const proxy of additions) { + dedup.set(proxy.url, { ...proxy }); + } + + return [...dedup.values()]; +} + +function proxyStatus(proxy: ProxyConfig): string { + return proxy.enabled === false ? "disabled" : "enabled"; +} + +function printProxyList(proxies: ProxyConfig[]): void { + if (proxies.length === 0) { + console.log(" (no proxies configured)"); + return; + } + + for (const [index, proxy] of proxies.entries()) { + console.log(` ${index + 1}. ${proxy.url} [${proxyStatus(proxy)}]`); + } +} + +function parseProxyIndex(inputValue: string, max: number): number | null { + const parsed = Number.parseInt(inputValue.trim(), 10); + if (!Number.isFinite(parsed) || parsed < 1 || parsed > max) { + return null; + } + return parsed - 1; +} + +interface ProxyManagerOptions { + doneLabel: string; + cancelResult: ProxyConfig[] | undefined; +} + +async function promptProxyManager( + rl: ReturnType, + accountLabel: string, + initialProxies: ProxyConfig[], + options: ProxyManagerOptions, +): Promise { + let working = cloneProxies(initialProxies); + + while (true) { + console.log(`\nProxy manager for ${accountLabel}:`); + printProxyList(working); + console.log("\nActions:"); + console.log(" [a] Add proxy"); + console.log(" [d] Delete one proxy"); + console.log(" [t] Toggle one proxy enabled/disabled"); + console.log(" [c] Clear all proxies"); + console.log(` [s] ${options.doneLabel}`); + console.log(" [x] Cancel"); + + const action = (await rl.question("Choose action [a/d/t/c/s/x]: ")).trim().toLowerCase(); + + if (action === "x" || action === "cancel") { + return options.cancelResult; + } + + if (action === "s" || action === "save" || action === "done") { + return cloneProxies(working); + } + + if (action === "c" || action === "clear") { + working = []; + console.log("Cleared all proxies."); + continue; + } + + if (action === "a" || action === "add") { + console.log("Supported proxy URL schemes: http:// https:// socks4:// socks4a:// socks5:// socks5h://"); + console.log("Tip: prefix a URL with '!' to add as disabled."); + const raw = await rl.question("Proxy URL(s) to add (comma-separated): "); + const trimmed = raw.trim(); + if (!trimmed) { + console.log("No input provided."); + continue; + } + + const { proxies, invalid } = parseProxyInput(trimmed); + if (invalid.length > 0) { + console.log(`Invalid proxy URL(s): ${invalid.join(", ")}`); + continue; + } + if (proxies.length === 0) { + console.log("No valid proxies provided."); + continue; + } + + working = mergeProxyConfigs(working, proxies); + console.log(`Added/updated ${proxies.length} proxy item(s).`); + continue; + } + + if (action === "d" || action === "delete") { + if (working.length === 0) { + console.log("No proxies to delete."); + continue; + } + + const selection = await rl.question(`Delete proxy number [1-${working.length}]: `); + const proxyIndex = parseProxyIndex(selection, working.length); + if (proxyIndex === null) { + console.log("Invalid selection."); + continue; + } + + const removed = working[proxyIndex]; + working = working.filter((_, index) => index !== proxyIndex); + console.log(`Deleted proxy: ${removed?.url ?? "unknown"}`); + continue; + } + + if (action === "t" || action === "toggle") { + if (working.length === 0) { + console.log("No proxies to toggle."); + continue; + } + + const selection = await rl.question(`Toggle proxy number [1-${working.length}]: `); + const proxyIndex = parseProxyIndex(selection, working.length); + if (proxyIndex === null) { + console.log("Invalid selection."); + continue; + } + + const target = working[proxyIndex]; + if (!target) { + console.log("Proxy not found."); + continue; + } + + working[proxyIndex] = target.enabled === false + ? { url: target.url } + : { url: target.url, enabled: false }; + + const state = working[proxyIndex]?.enabled === false ? "disabled" : "enabled"; + console.log(`Proxy ${proxyIndex + 1} is now ${state}.`); + continue; + } + + console.log("Unknown action. Use a/d/t/c/s/x."); + } +} export async function promptProjectId(): Promise { const rl = createInterface({ input, output }); @@ -30,6 +242,49 @@ export async function promptAddAnotherAccount(currentCount: number): Promise { + const rl = createInterface({ input, output }); + try { + if (existingProxies.length > 0) { + const reuse = await rl.question("Reuse existing proxies for this account? [Y/n]: "); + const reuseNormalized = reuse.trim().toLowerCase(); + if (!reuseNormalized || reuseNormalized === "y" || reuseNormalized === "yes") { + return cloneProxies(existingProxies); + } + } + + const useProxy = await rl.question("Use proxy for this OAuth account before login redirect? [y/N]: "); + const normalized = useProxy.trim().toLowerCase(); + if (!(normalized === "y" || normalized === "yes")) { + return []; + } + + const managed = await promptProxyManager(rl, "OAuth account", existingProxies, { + doneLabel: "Continue OAuth with these proxy settings", + cancelResult: [], + }); + + return managed ?? []; + } finally { + rl.close(); + } +} + +export async function promptAccountProxyConfiguration( + accountLabel: string, + existingProxies: ProxyConfig[] = [], +): Promise { + const rl = createInterface({ input, output }); + try { + return await promptProxyManager(rl, accountLabel, existingProxies, { + doneLabel: "Save proxy settings", + cancelResult: undefined, + }); + } finally { + rl.close(); + } +} + export type LoginMode = "add" | "fresh" | "manage" | "check" | "verify" | "verify-all" | "cancel"; export interface ExistingAccountInfo { @@ -47,6 +302,7 @@ export interface LoginMenuResult { deleteAccountIndex?: number; refreshAccountIndex?: number; toggleAccountIndex?: number; + configureProxyAccountIndex?: number; verifyAccountIndex?: number; verifyAll?: boolean; deleteAll?: boolean; @@ -63,7 +319,7 @@ async function promptLoginModeFallback(existingAccounts: ExistingAccountInfo[]): console.log(""); while (true) { - const answer = await rl.question("(a)dd new, (f)resh start, (c)heck quotas, (v)erify account, (va) verify all? [a/f/c/v/va]: "); + const answer = await rl.question("(a)dd new, (f)resh start, (c)heck quotas, (v)erify account, (va) verify all, manage (p)roxy? [a/f/c/v/va/p]: "); const normalized = answer.trim().toLowerCase(); if (normalized === "a" || normalized === "add") { @@ -81,8 +337,23 @@ async function promptLoginModeFallback(existingAccounts: ExistingAccountInfo[]): if (normalized === "va" || normalized === "verify-all" || normalized === "all") { return { mode: "verify-all", verifyAll: true }; } + if (normalized === "p" || normalized === "proxy") { + if (existingAccounts.length === 0) { + console.log("No accounts available for proxy management."); + continue; + } + + const pick = await rl.question(`Account number to manage proxy [1-${existingAccounts.length}]: `); + const pickedIndex = parseProxyIndex(pick, existingAccounts.length); + if (pickedIndex === null) { + console.log("Invalid account selection."); + continue; + } - console.log("Please enter 'a', 'f', 'c', 'v', or 'va'."); + return { mode: "manage", configureProxyAccountIndex: pickedIndex }; + } + + console.log("Please enter 'a', 'f', 'c', 'v', 'va', or 'p'."); } } finally { rl.close(); @@ -133,6 +404,9 @@ export async function promptLoginMode(existingAccounts: ExistingAccountInfo[]): if (accountAction === "toggle") { return { mode: "manage", toggleAccountIndex: action.account.index }; } + if (accountAction === "proxy") { + return { mode: "manage", configureProxyAccountIndex: action.account.index }; + } if (accountAction === "verify") { return { mode: "verify", verifyAccountIndex: action.account.index }; } diff --git a/src/plugin/project.test.ts b/src/plugin/project.test.ts new file mode 100644 index 0000000..09a8e76 --- /dev/null +++ b/src/plugin/project.test.ts @@ -0,0 +1,77 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" + +const { fetchWithProxyMock } = vi.hoisted(() => ({ + fetchWithProxyMock: vi.fn(), +})) + +vi.mock("./proxy", () => ({ + fetchWithProxy: fetchWithProxyMock, +})) + +import { loadManagedProject, onboardManagedProject } from "./project" + +describe("project proxy routing", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("routes loadManagedProject through fetchWithProxy with account-scoped proxy args", async () => { + fetchWithProxyMock.mockResolvedValueOnce( + new Response( + JSON.stringify({ cloudaicompanionProject: "managed-project" }), + { status: 200 }, + ), + ) + + const proxies = [{ url: "http://127.0.0.1:8080" }] + const result = await loadManagedProject("access-token", "my-project", proxies, 4) + + expect(result?.cloudaicompanionProject).toBe("managed-project") + expect(fetchWithProxyMock).toHaveBeenCalledTimes(1) + + const [url, init, passedProxies, passedAccountIndex] = fetchWithProxyMock.mock.calls[0] ?? [] + expect(typeof url).toBe("string") + expect(url).toContain("/v1internal:loadCodeAssist") + expect((init as RequestInit | undefined)?.method).toBe("POST") + expect(passedProxies).toEqual(proxies) + expect(passedAccountIndex).toBe(4) + }) + + it("routes onboardManagedProject through fetchWithProxy with account-scoped proxy args", async () => { + fetchWithProxyMock.mockResolvedValueOnce( + new Response( + JSON.stringify({ + done: true, + response: { + cloudaicompanionProject: { + id: "managed-onboarded", + }, + }, + }), + { status: 200 }, + ), + ) + + const proxies = [{ url: "socks5://127.0.0.1:1080" }] + const result = await onboardManagedProject( + "access-token", + "FREE", + "my-project", + proxies, + 6, + 1, + 0, + ) + + expect(result).toBe("managed-onboarded") + expect(fetchWithProxyMock).toHaveBeenCalledTimes(1) + + const [url, init, passedProxies, passedAccountIndex] = fetchWithProxyMock.mock.calls[0] ?? [] + expect(typeof url).toBe("string") + expect(url).toContain("/v1internal:onboardUser") + expect((init as RequestInit | undefined)?.method).toBe("POST") + expect(passedProxies).toEqual(proxies) + expect(passedAccountIndex).toBe(6) + }) +}) + diff --git a/src/plugin/project.ts b/src/plugin/project.ts index 6243cfd..c8a335a 100644 --- a/src/plugin/project.ts +++ b/src/plugin/project.ts @@ -6,6 +6,8 @@ import { } from "../constants"; import { formatRefreshParts, parseRefreshParts } from "./auth"; import { createLogger } from "./logger"; +import { fetchWithProxy } from "./proxy"; +import type { ProxyConfig } from "./storage"; import type { OAuthAuthDetails, ProjectContextResult } from "./types"; const log = createLogger("project"); @@ -121,6 +123,8 @@ export function invalidateProjectContextCache(refresh?: string): void { export async function loadManagedProject( accessToken: string, projectId?: string, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { const metadata = buildMetadata(projectId); const requestBody: Record = { metadata }; @@ -139,13 +143,15 @@ export async function loadManagedProject( for (const baseEndpoint of loadEndpoints) { try { - const response = await fetch( + const response = await fetchWithProxy( `${baseEndpoint}/v1internal:loadCodeAssist`, { method: "POST", headers: loadHeaders, body: JSON.stringify(requestBody), }, + proxies, + accountIndex, ); if (!response.ok) { @@ -170,6 +176,8 @@ export async function onboardManagedProject( accessToken: string, tierId: string, projectId?: string, + proxies?: ProxyConfig[], + accountIndex?: number, attempts = 10, delayMs = 5000, ): Promise { @@ -182,7 +190,7 @@ export async function onboardManagedProject( for (const baseEndpoint of ANTIGRAVITY_ENDPOINT_FALLBACKS) { for (let attempt = 0; attempt < attempts; attempt += 1) { try { - const response = await fetch( + const response = await fetchWithProxy( `${baseEndpoint}/v1internal:onboardUser`, { method: "POST", @@ -193,6 +201,8 @@ export async function onboardManagedProject( }, body: JSON.stringify(requestBody), }, + proxies, + accountIndex, ); if (!response.ok) { @@ -222,7 +232,11 @@ export async function onboardManagedProject( /** * Resolves an effective project ID for the current auth state, caching results per refresh token. */ -export async function ensureProjectContext(auth: OAuthAuthDetails): Promise { +export async function ensureProjectContext( + auth: OAuthAuthDetails, + proxies?: ProxyConfig[], + accountIndex?: number, +): Promise { const accessToken = auth.access; if (!accessToken) { return { auth, effectiveProjectId: "" }; @@ -261,7 +275,12 @@ export async function ensureProjectContext(auth: OAuthAuthDetails): Promise { + return { + proxyAgentCtor: vi.fn(function MockProxyAgent(options: { uri: string }) { + return { + kind: "proxy-agent", + options, + } + }), + socksDispatcherFactory: vi.fn((options: { + type: 4 | 5 + host: string + port: number + userId?: string + password?: string + }) => { + return { + kind: "socks-dispatcher", + options, + } + }), + } +}) + +vi.mock("undici", () => ({ + ProxyAgent: proxyAgentCtor, +})) + +vi.mock("fetch-socks", () => ({ + socksDispatcher: socksDispatcherFactory, +})) + +import { + fetchWithProxy, + ProxyExhaustedError, + resetProxyState, + __testExports, +} from "./proxy.ts" + +const { + isProxyConnectionError, + calculateCooldownMs, + redactUrl, + proxyStates, + dispatcherCache, +} = __testExports + +function createConnectionError(code: string, message = code): Error { + return Object.assign(new Error(message), { code }) +} + +function proxy(url: string, enabled = true): ProxyConfig { + return { url, enabled } +} + +function getDispatcherFromCall(fetchMock: { + mock: { + calls: unknown[][] + } +}, callIndex: number): { + kind?: string + options?: { uri?: string; host?: string; port?: number; type?: number; userId?: string; password?: string } +} { + const init = fetchMock.mock.calls[callIndex]?.[1] as + | (RequestInit & { + dispatcher?: { + kind?: string + options?: { + uri?: string + host?: string + port?: number + type?: number + userId?: string + password?: string + } + } + }) + | undefined + return init?.dispatcher ?? {} +} + +describe("fetchWithProxy", () => { + beforeEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() + vi.clearAllMocks() + resetProxyState() + }) + + afterEach(() => { + vi.restoreAllMocks() + resetProxyState() + }) + + describe("core behavior", () => { + it("uses direct fetch when proxies are undefined", async () => { + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("ok")) + + const result = await fetchWithProxy("https://example.com") + + expect(result.status).toBe(200) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(fetchMock).toHaveBeenCalledWith("https://example.com", undefined) + expect(dispatcherCache.size).toBe(0) + }) + + it("uses direct fetch when proxies are empty", async () => { + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("ok")) + + const result = await fetchWithProxy("https://example.com", { method: "POST" }, []) + + expect(result.status).toBe(200) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(fetchMock).toHaveBeenCalledWith("https://example.com", { method: "POST" }) + expect(dispatcherCache.size).toBe(0) + }) + + it("fails closed when proxies are configured but all are disabled", async () => { + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("ok")) + + const promise = fetchWithProxy( + "https://example.com", + undefined, + [ + proxy("http://disabled-1.local:8080", false), + proxy("http://disabled-2.local:8080", false), + ], + 2, + ) + + await expect(promise).rejects.toMatchObject({ + name: "ProxyExhaustedError", + accountIndex: 2, + proxyCount: 0, + }) + expect(fetchMock).not.toHaveBeenCalled() + expect(proxyStates.size).toBe(0) + expect(dispatcherCache.size).toBe(0) + }) + + it("routes through the first available proxy", async () => { + const firstProxy = "http://proxy-1.local:8080" + const secondProxy = "http://proxy-2.local:8080" + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("proxied")) + + const response = await fetchWithProxy( + "https://example.com", + { method: "GET" }, + [proxy(firstProxy), proxy(secondProxy)], + ) + + const dispatcher = getDispatcherFromCall(fetchMock, 0) + expect(response.status).toBe(200) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(dispatcher.options?.uri).toBe(firstProxy) + expect(dispatcherCache.size).toBe(1) + }) + + it("fails over sequentially on proxy connection errors", async () => { + const firstProxy = "http://proxy-1.local:8080" + const secondProxy = "http://proxy-2.local:8080" + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(createConnectionError("ECONNREFUSED")) + .mockResolvedValueOnce(new Response("ok")) + + const response = await fetchWithProxy( + "https://example.com", + undefined, + [proxy(firstProxy), proxy(secondProxy)], + 5, + ) + + expect(response.status).toBe(200) + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(getDispatcherFromCall(fetchMock, 0).options?.uri).toBe(firstProxy) + expect(getDispatcherFromCall(fetchMock, 1).options?.uri).toBe(secondProxy) + expect(proxyStates.get(`5:${firstProxy}`)?.failCount).toBe(1) + expect(proxyStates.get(`5:${secondProxy}`)).toBeUndefined() + }) + + it.each([400, 500, 503])("does not fail over on HTTP %i responses", async status => { + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("upstream", { status })) + + const response = await fetchWithProxy( + "https://example.com", + undefined, + [proxy("http://proxy-1.local:8080"), proxy("http://proxy-2.local:8080")], + ) + + expect(response.status).toBe(status) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(proxyStates.size).toBe(0) + }) + + it("throws ProxyExhaustedError when all available proxies fail", async () => { + vi.spyOn(globalThis, "fetch") + .mockRejectedValueOnce(createConnectionError("ETIMEDOUT")) + .mockRejectedValueOnce(createConnectionError("ECONNRESET")) + + const promise = fetchWithProxy( + "https://example.com", + undefined, + [proxy("http://proxy-1.local:8080"), proxy("http://proxy-2.local:8080")], + 1, + ) + + await expect(promise).rejects.toBeInstanceOf(ProxyExhaustedError) + await expect(promise).rejects.toMatchObject({ accountIndex: 1, proxyCount: 2 }) + expect(proxyStates.get("1:http://proxy-1.local:8080")?.failCount).toBe(1) + expect(proxyStates.get("1:http://proxy-2.local:8080")?.failCount).toBe(1) + }) + + it("throws ProxyExhaustedError when all enabled proxies are in cooldown", async () => { + const firstProxy = "http://proxy-1.local:8080" + const secondProxy = "http://proxy-2.local:8080" + const now = new Date("2025-01-01T00:00:00.000Z").getTime() + + vi.useFakeTimers() + vi.setSystemTime(now) + proxyStates.set(`4:${firstProxy}`, { + failCount: 1, + lastFailTime: now, + cooldownUntil: now + 5_000, + }) + proxyStates.set(`4:${secondProxy}`, { + failCount: 2, + lastFailTime: now, + cooldownUntil: now + 15_000, + }) + + const fetchMock = vi.spyOn(globalThis, "fetch") + const promise = fetchWithProxy( + "https://example.com", + undefined, + [proxy(firstProxy), proxy(secondProxy)], + 4, + ) + + await expect(promise).rejects.toMatchObject({ + name: "ProxyExhaustedError", + accountIndex: 4, + proxyCount: 2, + }) + expect(fetchMock).not.toHaveBeenCalled() + }) + + it("propagates AbortError without failover", async () => { + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(new DOMException("aborted", "AbortError")) + + const promise = fetchWithProxy( + "https://example.com", + undefined, + [proxy("http://proxy-1.local:8080"), proxy("http://proxy-2.local:8080")], + ) + + await expect(promise).rejects.toMatchObject({ name: "AbortError" }) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(proxyStates.size).toBe(0) + }) + + it("propagates TypeError without failover", async () => { + const fetchMock = vi.spyOn(globalThis, "fetch").mockRejectedValueOnce(new TypeError("invalid url")) + + const promise = fetchWithProxy( + "https://example.com", + undefined, + [proxy("http://proxy-1.local:8080"), proxy("http://proxy-2.local:8080")], + ) + + await expect(promise).rejects.toBeInstanceOf(TypeError) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(proxyStates.size).toBe(0) + }) + + it("fails over on TypeError when cause has proxy error code", async () => { + const firstProxy = "http://proxy-1.local:8080" + const secondProxy = "http://proxy-2.local:8080" + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(new TypeError("fetch failed", { cause: createConnectionError("ECONNREFUSED") })) + .mockResolvedValueOnce(new Response("ok")) + + const response = await fetchWithProxy( + "https://example.com", + undefined, + [proxy(firstProxy), proxy(secondProxy)], + 6, + ) + + expect(response.status).toBe(200) + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(getDispatcherFromCall(fetchMock, 0).options?.uri).toBe(firstProxy) + expect(getDispatcherFromCall(fetchMock, 1).options?.uri).toBe(secondProxy) + expect(proxyStates.get(`6:${firstProxy}`)?.failCount).toBe(1) + expect(proxyStates.get(`6:${secondProxy}`)).toBeUndefined() + }) + it.each(["ENOTFOUND", "EAI_AGAIN"])("fails over on DNS error code %s in TypeError cause", async (code) => { + const firstProxy = "http://proxy-1.local:8080" + const secondProxy = "http://proxy-2.local:8080" + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(new TypeError("fetch failed", { cause: createConnectionError(code) })) + .mockResolvedValueOnce(new Response("ok")) + + const response = await fetchWithProxy( + "https://example.com", + undefined, + [proxy(firstProxy), proxy(secondProxy)], + 8, + ) + + expect(response.status).toBe(200) + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(getDispatcherFromCall(fetchMock, 0).options?.uri).toBe(firstProxy) + expect(getDispatcherFromCall(fetchMock, 1).options?.uri).toBe(secondProxy) + expect(proxyStates.get(`8:${firstProxy}`)?.failCount).toBe(1) + expect(proxyStates.get(`8:${secondProxy}`)).toBeUndefined() + }) + + it("skips cooled-down proxies and uses next available proxy", async () => { + vi.useFakeTimers() + vi.setSystemTime(new Date("2025-01-01T00:00:00.000Z")) + + const firstProxy = "http://proxy-1.local:8080" + const secondProxy = "http://proxy-2.local:8080" + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(createConnectionError("ECONNREFUSED")) + .mockResolvedValueOnce(new Response("ok")) + .mockResolvedValueOnce(new Response("ok-again")) + + await fetchWithProxy( + "https://example.com", + undefined, + [proxy(firstProxy), proxy(secondProxy)], + 3, + ) + + await fetchWithProxy( + "https://example.com/next", + undefined, + [proxy(firstProxy), proxy(secondProxy)], + 3, + ) + + expect(fetchMock).toHaveBeenCalledTimes(3) + expect(getDispatcherFromCall(fetchMock, 0).options?.uri).toBe(firstProxy) + expect(getDispatcherFromCall(fetchMock, 1).options?.uri).toBe(secondProxy) + expect(getDispatcherFromCall(fetchMock, 2).options?.uri).toBe(secondProxy) + + const failedProxyState = proxyStates.get(`3:${firstProxy}`) + expect(failedProxyState?.failCount).toBe(1) + expect(failedProxyState?.cooldownUntil).toBe(Date.now() + 5_000) + }) + + it("resets a proxy failure state after successful request", async () => { + const targetProxy = "http://proxy-1.local:8080" + proxyStates.set(`0:${targetProxy}`, { + failCount: 3, + lastFailTime: 111, + cooldownUntil: 0, + }) + + vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("ok")) + await fetchWithProxy("https://example.com", undefined, [proxy(targetProxy)], 0) + + expect(proxyStates.get(`0:${targetProxy}`)).toBeUndefined() + }) + }) + + describe("integration scenarios", () => { + it("isolates proxy health state between account indexes", async () => { + vi.useFakeTimers() + vi.setSystemTime(new Date("2025-01-01T00:00:00.000Z")) + + const sharedProxy = "http://shared.proxy.local:8080" + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(createConnectionError("ECONNRESET")) + .mockResolvedValueOnce(new Response("ok")) + + await expect( + fetchWithProxy("https://example.com/a", undefined, [proxy(sharedProxy)], 0), + ).rejects.toBeInstanceOf(ProxyExhaustedError) + + const response = await fetchWithProxy( + "https://example.com/b", + undefined, + [proxy(sharedProxy)], + 1, + ) + + expect(response.status).toBe(200) + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(proxyStates.get(`0:${sharedProxy}`)?.failCount).toBe(1) + expect(proxyStates.get(`1:${sharedProxy}`)).toBeUndefined() + }) + + it("keeps per-request proxy selection isolated", async () => { + const accountIndex = 7 + const proxyA = "http://proxy-a.local:8080" + const proxyB = "http://proxy-b.local:8080" + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockResolvedValueOnce(new Response("first")) + .mockResolvedValueOnce(new Response("second")) + + await fetchWithProxy("https://example.com/one", undefined, [proxy(proxyA)], accountIndex) + await fetchWithProxy("https://example.com/two", undefined, [proxy(proxyB)], accountIndex) + + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(getDispatcherFromCall(fetchMock, 0).options?.uri).toBe(proxyA) + expect(getDispatcherFromCall(fetchMock, 1).options?.uri).toBe(proxyB) + expect(dispatcherCache.size).toBe(2) + expect(proxyStates.size).toBe(0) + }) + + it("supports different proxy chains for multiple accounts", async () => { + const account0Primary = "http://a0-primary.local:8080" + const account0Secondary = "http://a0-secondary.local:8080" + const account1Primary = "http://a1-primary.local:8080" + + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(createConnectionError("ECONNREFUSED")) + .mockResolvedValueOnce(new Response("account0-ok")) + .mockResolvedValueOnce(new Response("account1-ok")) + + await fetchWithProxy( + "https://example.com/account-0", + undefined, + [proxy(account0Primary), proxy(account0Secondary)], + 0, + ) + await fetchWithProxy( + "https://example.com/account-1", + undefined, + [proxy(account1Primary)], + 1, + ) + + expect(fetchMock).toHaveBeenCalledTimes(3) + expect(getDispatcherFromCall(fetchMock, 0).options?.uri).toBe(account0Primary) + expect(getDispatcherFromCall(fetchMock, 1).options?.uri).toBe(account0Secondary) + expect(getDispatcherFromCall(fetchMock, 2).options?.uri).toBe(account1Primary) + expect(proxyStates.get(`0:${account0Primary}`)?.failCount).toBe(1) + expect(proxyStates.get(`1:${account1Primary}`)).toBeUndefined() + }) + }) +}) + +describe("isProxyConnectionError", () => { + it.each([ + "UND_ERR_SOCKET", + "UND_ERR_CONNECT_TIMEOUT", + "UND_ERR_PRX_TLS", + "ECONNREFUSED", + "ECONNRESET", + "ETIMEDOUT", + "ENETUNREACH", + "EHOSTUNREACH", + "EPIPE", + ])("returns true for code %s", code => { + expect(isProxyConnectionError(createConnectionError(code))).toBe(true) + }) + + it("returns false for AbortError", () => { + expect(isProxyConnectionError(new DOMException("aborted", "AbortError"))).toBe(false) + }) + + it("returns false for TypeError", () => { + expect(isProxyConnectionError(new TypeError("invalid url"))).toBe(false) + }) + + it("returns true for TypeError with proxy failure cause", () => { + const error = new TypeError("fetch failed", { cause: createConnectionError("ECONNREFUSED") }) + expect(isProxyConnectionError(error)).toBe(true) + }) + + it.each([ + "Proxy authentication required", + "Tunnel connection failed", + "SOCKS handshake failed", + "socket hang up", + "connect ECONNREFUSED 127.0.0.1:8080", + ])("uses message fallback for '%s'", message => { + expect(isProxyConnectionError(new Error(message))).toBe(true) + }) + + it("returns false for unrelated Error messages", () => { + expect(isProxyConnectionError(new Error("invalid JSON payload"))).toBe(false) + }) + + it("returns true for non-Error unknown values", () => { + expect(isProxyConnectionError("proxy failed")).toBe(true) + }) +}) + +describe("cooldown progression", () => { + it("returns expected durations for each failure tier", () => { + expect(calculateCooldownMs(1)).toBe(5_000) + expect(calculateCooldownMs(2)).toBe(15_000) + expect(calculateCooldownMs(3)).toBe(60_000) + expect(calculateCooldownMs(4)).toBe(300_000) + expect(calculateCooldownMs(12)).toBe(300_000) + }) + + it("escalates cooldown durations across repeated failures and caps at 300s", async () => { + vi.useFakeTimers() + let now = new Date("2025-01-01T00:00:00.000Z").getTime() + vi.setSystemTime(now) + + const targetProxy = "http://cooldown.proxy.local:8080" + vi.spyOn(globalThis, "fetch").mockRejectedValue(createConnectionError("ECONNRESET")) + + await expect( + fetchWithProxy("https://example.com/1", undefined, [proxy(targetProxy)], 9), + ).rejects.toBeInstanceOf(ProxyExhaustedError) + expect(proxyStates.get(`9:${targetProxy}`)?.cooldownUntil).toBe(now + 5_000) + expect(proxyStates.get(`9:${targetProxy}`)?.failCount).toBe(1) + + now += 5_001 + vi.setSystemTime(now) + await expect( + fetchWithProxy("https://example.com/2", undefined, [proxy(targetProxy)], 9), + ).rejects.toBeInstanceOf(ProxyExhaustedError) + expect(proxyStates.get(`9:${targetProxy}`)?.cooldownUntil).toBe(now + 15_000) + expect(proxyStates.get(`9:${targetProxy}`)?.failCount).toBe(2) + + now += 15_001 + vi.setSystemTime(now) + await expect( + fetchWithProxy("https://example.com/3", undefined, [proxy(targetProxy)], 9), + ).rejects.toBeInstanceOf(ProxyExhaustedError) + expect(proxyStates.get(`9:${targetProxy}`)?.cooldownUntil).toBe(now + 60_000) + expect(proxyStates.get(`9:${targetProxy}`)?.failCount).toBe(3) + + now += 60_001 + vi.setSystemTime(now) + await expect( + fetchWithProxy("https://example.com/4", undefined, [proxy(targetProxy)], 9), + ).rejects.toBeInstanceOf(ProxyExhaustedError) + expect(proxyStates.get(`9:${targetProxy}`)?.cooldownUntil).toBe(now + 300_000) + expect(proxyStates.get(`9:${targetProxy}`)?.failCount).toBe(4) + + now += 300_001 + vi.setSystemTime(now) + await expect( + fetchWithProxy("https://example.com/5", undefined, [proxy(targetProxy)], 9), + ).rejects.toBeInstanceOf(ProxyExhaustedError) + expect(proxyStates.get(`9:${targetProxy}`)?.cooldownUntil).toBe(now + 300_000) + expect(proxyStates.get(`9:${targetProxy}`)?.failCount).toBe(5) + }) +}) + +describe("redactUrl", () => { + it("redacts username and password credentials", () => { + expect(redactUrl("http://alice:s3cr3t@proxy.local:8080")).toBe("http://***:***@proxy.local:8080/") + }) + + it("preserves URLs without credentials", () => { + expect(redactUrl("http://proxy.local:8080")).toBe("http://proxy.local:8080/") + }) + + it("handles invalid URLs gracefully", () => { + expect(redactUrl("not a url")).toBe("[invalid-url]") + }) +}) + +describe("dispatcher factory", () => { + beforeEach(() => { + vi.restoreAllMocks() + vi.clearAllMocks() + resetProxyState() + }) + + it("uses ProxyAgent for HTTP and HTTPS proxy URLs", async () => { + const httpProxy = "http://http-proxy.local:8080" + const httpsProxy = "https://https-proxy.local:8443" + vi.spyOn(globalThis, "fetch") + .mockResolvedValueOnce(new Response("http")) + .mockResolvedValueOnce(new Response("https")) + + await fetchWithProxy("https://example.com/http", undefined, [proxy(httpProxy)], 0) + await fetchWithProxy("https://example.com/https", undefined, [proxy(httpsProxy)], 0) + + expect(proxyAgentCtor).toHaveBeenCalledTimes(2) + expect(proxyAgentCtor).toHaveBeenNthCalledWith(1, { uri: httpProxy }) + expect(proxyAgentCtor).toHaveBeenNthCalledWith(2, { uri: httpsProxy }) + expect(socksDispatcherFactory).not.toHaveBeenCalled() + }) + + it("uses socksDispatcher for SOCKS proxies with decoded credentials", async () => { + const socksProxy = "socks5://user%20name:p%40ss@socks.local:1234" + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("ok")) + + await fetchWithProxy("https://example.com", undefined, [proxy(socksProxy)], 0) + + expect(socksDispatcherFactory).toHaveBeenCalledTimes(1) + expect(socksDispatcherFactory).toHaveBeenCalledWith({ + type: 5, + host: "socks.local", + port: 1234, + userId: "user name", + password: "p@ss", + }) + + const dispatcher = getDispatcherFromCall(fetchMock, 0) + expect(dispatcher.kind).toBe("socks-dispatcher") + }) + + it("uses SOCKS4 defaults when port is omitted", async () => { + const socksProxy = "socks4a://legacy-socks.local" + vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("ok")) + + await fetchWithProxy("https://example.com", undefined, [proxy(socksProxy)], 0) + + expect(socksDispatcherFactory).toHaveBeenCalledTimes(1) + expect(socksDispatcherFactory).toHaveBeenCalledWith({ + type: 4, + host: "legacy-socks.local", + port: 1080, + userId: undefined, + password: undefined, + }) + }) + + it("caches dispatchers by proxy URL", async () => { + const proxyUrl = "http://cached-proxy.local:8080" + const fetchMock = vi.spyOn(globalThis, "fetch") + .mockResolvedValueOnce(new Response("first")) + .mockResolvedValueOnce(new Response("second")) + + await fetchWithProxy("https://example.com/one", undefined, [proxy(proxyUrl)], 0) + await fetchWithProxy("https://example.com/two", undefined, [proxy(proxyUrl)], 0) + + expect(proxyAgentCtor).toHaveBeenCalledTimes(1) + expect(dispatcherCache.size).toBe(1) + expect(getDispatcherFromCall(fetchMock, 0)).toBe(getDispatcherFromCall(fetchMock, 1)) + }) +}) + diff --git a/src/plugin/proxy.ts b/src/plugin/proxy.ts new file mode 100644 index 0000000..59928a6 --- /dev/null +++ b/src/plugin/proxy.ts @@ -0,0 +1,262 @@ +import { ProxyAgent } from "undici" +import type { Dispatcher } from "undici" +import { socksDispatcher } from "fetch-socks" +import type { ProxyConfig } from "./storage" +import { createLogger } from "./logger" + +const log = createLogger("proxy") + +interface ProxiedRequestInit extends RequestInit { + dispatcher?: Dispatcher +} + +interface ProxyRuntimeState { + failCount: number + lastFailTime: number + cooldownUntil: number +} + +export class ProxyExhaustedError extends Error { + readonly proxyCount: number + readonly accountIndex: number + + constructor(accountIndex: number, proxyCount: number) { + super( + proxyCount > 0 + ? ( + `All ${proxyCount} proxy(ies) failed for account ${accountIndex}. ` + + `Check proxy connectivity or remove failing proxies.` + ) + : `No enabled proxies configured for account ${accountIndex}. Refusing direct connection to prevent IP leakage.` + ) + this.name = "ProxyExhaustedError" + this.proxyCount = proxyCount + this.accountIndex = accountIndex + } +} + +const PROXY_COOLDOWN_PROGRESSION_MS = [5_000, 15_000, 60_000, 300_000] as const +const PROXY_ERROR_CODES = [ + "UND_ERR_SOCKET", + "UND_ERR_CONNECT_TIMEOUT", + "UND_ERR_PRX_TLS", + "ECONNREFUSED", + "ECONNRESET", + "ETIMEDOUT", + "ENETUNREACH", + "EHOSTUNREACH", + "ENOTFOUND", + "EAI_AGAIN", + "EPIPE", +] as const + +const dispatcherCache = new Map() +const proxyStates = new Map() + +function stateKey(accountIndex: number, proxyUrl: string): string { + return `${accountIndex}:${proxyUrl}` +} + +function calculateCooldownMs(failCount: number): number { + const idx = Math.min(failCount - 1, PROXY_COOLDOWN_PROGRESSION_MS.length - 1) + return PROXY_COOLDOWN_PROGRESSION_MS[idx] ?? 300_000 +} + +function getOrCreateDispatcher(proxyUrl: string): Dispatcher { + const cached = dispatcherCache.get(proxyUrl) + if (cached) return cached + + const url = new URL(proxyUrl) + const protocol = url.protocol.replace(":", "") + let dispatcher: Dispatcher + + if (protocol === "socks5" || protocol === "socks4" || + protocol === "socks5h" || protocol === "socks4a") { + dispatcher = socksDispatcher({ + type: protocol.startsWith("socks5") ? 5 : 4, + host: url.hostname, + port: parseInt(url.port, 10) || 1080, + userId: url.username ? decodeURIComponent(url.username) : undefined, + password: url.password ? decodeURIComponent(url.password) : undefined, + }) + } else { + dispatcher = new ProxyAgent({ uri: proxyUrl }) + } + + dispatcherCache.set(proxyUrl, dispatcher) + return dispatcher +} + +function isAvailable(accountIndex: number, proxy: ProxyConfig): boolean { + if (proxy.enabled === false) return false + const state = proxyStates.get(stateKey(accountIndex, proxy.url)) + if (!state) return true + return Date.now() >= state.cooldownUntil +} + +function markFailed(accountIndex: number, proxy: ProxyConfig): void { + const key = stateKey(accountIndex, proxy.url) + const existing = proxyStates.get(key) + const failCount = (existing?.failCount ?? 0) + 1 + const now = Date.now() + proxyStates.set(key, { + failCount, + lastFailTime: now, + cooldownUntil: now + calculateCooldownMs(failCount), + }) + log.warn("Proxy marked failed", { + proxy: redactUrl(proxy.url), + failCount, + cooldownMs: calculateCooldownMs(failCount), + }) +} + +function markSuccess(accountIndex: number, proxy: ProxyConfig): void { + proxyStates.delete(stateKey(accountIndex, proxy.url)) +} + +function getErrorChain(error: Error): unknown[] { + const chain: unknown[] = [error] + let cursor: unknown = error + let depth = 0 + + while (depth < 4 && cursor && typeof cursor === "object") { + const cause = (cursor as { cause?: unknown }).cause + if (!cause) { + break + } + chain.push(cause) + cursor = cause + depth += 1 + } + + return chain +} + +function getErrorCode(value: unknown): string | undefined { + if (!value || typeof value !== "object") return undefined + const code = (value as { code?: unknown }).code + return typeof code === "string" ? code : undefined +} + +function getErrorName(value: unknown): string | undefined { + if (!value || typeof value !== "object") return undefined + const name = (value as { name?: unknown }).name + return typeof name === "string" ? name : undefined +} + +function getErrorMessage(value: unknown): string { + if (!value || typeof value !== "object") return "" + const message = (value as { message?: unknown }).message + return typeof message === "string" ? message.toLowerCase() : "" +} + +function hasProxyErrorMessage(message: string): boolean { + return ( + message.includes("proxy") || + message.includes("tunnel") || + message.includes("socks") || + message.includes("socket hang up") || + message.includes("connect econnrefused") + ) +} + +function isProxyConnectionError(error: unknown): boolean { + if (!(error instanceof Error)) return true + + for (const entry of getErrorChain(error)) { + const name = getErrorName(entry) + if (name === "AbortError") { + return false + } + + const code = getErrorCode(entry) + if (code && PROXY_ERROR_CODES.includes(code as (typeof PROXY_ERROR_CODES)[number])) { + return true + } + + const message = getErrorMessage(entry) + if (hasProxyErrorMessage(message)) { + return true + } + } + + if (error instanceof TypeError) { + return false + } + + return false +} + +export { isProxyConnectionError as isProxyError } + +function redactUrl(url: string): string { + try { + const parsed = new URL(url) + if (parsed.username) parsed.username = "***" + if (parsed.password) parsed.password = "***" + return parsed.toString() + } catch { + return "[invalid-url]" + } +} + +export async function fetchWithProxy( + input: RequestInfo | URL, + init?: RequestInit, + proxies?: ProxyConfig[], + accountIndex?: number, +): Promise { + if (!proxies || proxies.length === 0) { + return fetch(input, init) + } + + const acctIdx = accountIndex ?? 0 + const enabledCount = proxies.filter(p => p.enabled !== false).length + if (enabledCount === 0) { + throw new ProxyExhaustedError(acctIdx, 0) + } + + const available = proxies.filter(p => isAvailable(acctIdx, p)) + if (available.length === 0) { + throw new ProxyExhaustedError(acctIdx, enabledCount) + } + + for (const proxy of available) { + try { + const dispatcher = getOrCreateDispatcher(proxy.url) + const proxiedInit: ProxiedRequestInit = { ...init, dispatcher } + + log.debug("Fetching via proxy", { + proxy: redactUrl(proxy.url), + accountIndex: acctIdx, + }) + + const response = await fetch(input, proxiedInit as RequestInit) + markSuccess(acctIdx, proxy) + return response + } catch (error) { + if (!isProxyConnectionError(error)) { + throw error + } + + markFailed(acctIdx, proxy) + } + } + + throw new ProxyExhaustedError(acctIdx, available.length) +} + +export function resetProxyState(): void { + proxyStates.clear() + dispatcherCache.clear() +} + +export const __testExports = { + isProxyConnectionError, + calculateCooldownMs, + redactUrl, + proxyStates, + dispatcherCache, +} + diff --git a/src/plugin/quota-fallback.test.ts b/src/plugin/quota-fallback.test.ts index 2a56fb1..f218ccb 100644 --- a/src/plugin/quota-fallback.test.ts +++ b/src/plugin/quota-fallback.test.ts @@ -43,7 +43,7 @@ beforeAll(async () => { resolveHeaderRoutingDecision = (__testExports as { resolveHeaderRoutingDecision?: ResolveHeaderRoutingDecision; }).resolveHeaderRoutingDecision; -}); +}, 30000); describe("quota fallback direction", () => { it("falls back from gemini-cli to antigravity when alternate quota is available", () => { diff --git a/src/plugin/quota.test.ts b/src/plugin/quota.test.ts new file mode 100644 index 0000000..8c8e086 --- /dev/null +++ b/src/plugin/quota.test.ts @@ -0,0 +1,122 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" + +const { fetchWithProxyMock, refreshAccessTokenMock, ensureProjectContextMock } = vi.hoisted(() => ({ + fetchWithProxyMock: vi.fn(), + refreshAccessTokenMock: vi.fn(), + ensureProjectContextMock: vi.fn(), +})) + +vi.mock("./proxy", () => ({ + fetchWithProxy: fetchWithProxyMock, +})) + +vi.mock("./token", () => ({ + refreshAccessToken: refreshAccessTokenMock, +})) + +vi.mock("./project", () => ({ + ensureProjectContext: ensureProjectContextMock, +})) + +import { checkAccountsQuota } from "./quota" +import type { AccountMetadataV3 } from "./storage" +import type { OAuthAuthDetails, PluginClient } from "./types" + +describe("checkAccountsQuota proxy routing", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("passes account-scoped proxies/index to refresh, project context, and quota fetches", async () => { + const proxies = [{ url: "http://127.0.0.1:8080" }] + const accountIndex = 7 + const providerId = "antigravity" + + const refreshedAuth: OAuthAuthDetails = { + type: "oauth", + refresh: "refresh-token|project-1|managed-project-1", + access: "access-new", + expires: Date.now() + 60_000, + } + + refreshAccessTokenMock.mockResolvedValueOnce(refreshedAuth) + ensureProjectContextMock.mockResolvedValueOnce({ + auth: refreshedAuth, + effectiveProjectId: "managed-project-1", + }) + + fetchWithProxyMock + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + models: { + "gemini-3-pro": { + quotaInfo: { + remainingFraction: 0.8, + resetTime: "2026-02-15T12:00:00Z", + }, + }, + }, + }), + { status: 200 }, + ), + ) + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + buckets: [ + { + modelId: "gemini-3-pro", + remainingFraction: 0.6, + resetTime: "2026-02-15T10:00:00Z", + }, + ], + }), + { status: 200 }, + ), + ) + + const accounts: AccountMetadataV3[] = [ + { + refreshToken: "refresh-token", + projectId: "project-1", + addedAt: Date.now(), + lastUsed: Date.now(), + proxies, + }, + ] + + const client = { + auth: { + set: vi.fn(async () => {}), + }, + } as unknown as PluginClient + + const results = await checkAccountsQuota(accounts, client, providerId, [accountIndex]) + + expect(results[0]?.status).toBe("ok") + + expect(refreshAccessTokenMock).toHaveBeenCalledWith( + expect.objectContaining({ type: "oauth" }), + client, + providerId, + proxies, + accountIndex, + ) + + expect(ensureProjectContextMock).toHaveBeenCalledWith( + expect.objectContaining({ type: "oauth" }), + proxies, + accountIndex, + ) + + expect(fetchWithProxyMock).toHaveBeenCalledTimes(2) + for (const call of fetchWithProxyMock.mock.calls) { + expect(call[2]).toEqual(proxies) + expect(call[3]).toBe(accountIndex) + } + + expect(fetchWithProxyMock.mock.calls[0]?.[0]).toContain("/v1internal:fetchAvailableModels") + expect(fetchWithProxyMock.mock.calls[1]?.[0]).toContain("/v1internal:retrieveUserQuota") + }) +}) \ No newline at end of file diff --git a/src/plugin/quota.ts b/src/plugin/quota.ts index 895dbc8..7edfcfa 100644 --- a/src/plugin/quota.ts +++ b/src/plugin/quota.ts @@ -6,10 +6,11 @@ import { import { accessTokenExpired, formatRefreshParts, parseRefreshParts } from "./auth"; import { logQuotaFetch, logQuotaStatus } from "./debug"; import { ensureProjectContext } from "./project"; +import { fetchWithProxy } from "./proxy"; import { refreshAccessToken } from "./token"; import { getModelFamily } from "./transform/model-resolver"; import type { PluginClient, OAuthAuthDetails } from "./types"; -import type { AccountMetadataV3 } from "./storage"; +import type { AccountMetadataV3, ProxyConfig } from "./storage"; const FETCH_TIMEOUT_MS = 10000; @@ -172,11 +173,11 @@ function aggregateQuota(models?: Record): Quot return { groups, modelCount: totalCount }; } -async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS): Promise { +async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS, proxies?: ProxyConfig[], accountIndex?: number): Promise { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); try { - return await fetch(url, { ...options, signal: controller.signal }); + return await fetchWithProxy(url, { ...options, signal: controller.signal }, proxies, accountIndex); } finally { clearTimeout(timeout); } @@ -185,6 +186,8 @@ async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs = F async function fetchAvailableModels( accessToken: string, projectId: string, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { const endpoint = ANTIGRAVITY_ENDPOINT_PROD; const quotaUserAgent = getAntigravityHeaders()["User-Agent"] || "antigravity/windows/amd64"; @@ -199,7 +202,7 @@ async function fetchAvailableModels( "User-Agent": quotaUserAgent, }, body: JSON.stringify(body), - }); + }, FETCH_TIMEOUT_MS, proxies, accountIndex); if (response.ok) { return (await response.json()) as FetchAvailableModelsResponse; @@ -217,6 +220,8 @@ async function fetchAvailableModels( async function fetchGeminiCliQuota( accessToken: string, projectId: string, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { const endpoint = ANTIGRAVITY_ENDPOINT_PROD; // Use Gemini CLI user-agent to get CLI quota buckets (not Antigravity buckets) @@ -235,7 +240,7 @@ async function fetchGeminiCliQuota( "User-Agent": geminiCliUserAgent, }, body: JSON.stringify(body), - }); + }, FETCH_TIMEOUT_MS, proxies, accountIndex); if (response.ok) { const data = (await response.json()) as RetrieveUserQuotaResponse; @@ -311,6 +316,7 @@ export async function checkAccountsQuota( accounts: AccountMetadataV3[], client: PluginClient, providerId = ANTIGRAVITY_PROVIDER_ID, + accountIndexes?: number[], ): Promise { const results: AccountQuotaResult[] = []; @@ -318,19 +324,20 @@ export async function checkAccountsQuota( for (const [index, account] of accounts.entries()) { const disabled = account.enabled === false; + const effectiveAccountIndex = accountIndexes?.[index] ?? index; let auth = buildAuthFromAccount(account); try { if (accessTokenExpired(auth)) { - const refreshed = await refreshAccessToken(auth, client, providerId); + const refreshed = await refreshAccessToken(auth, client, providerId, account.proxies, effectiveAccountIndex); if (!refreshed) { throw new Error("Token refresh failed"); } auth = refreshed; } - const projectContext = await ensureProjectContext(auth); + const projectContext = await ensureProjectContext(auth, account.proxies, effectiveAccountIndex); auth = projectContext.auth; const updatedAccount = applyAccountUpdates(account, auth); @@ -339,9 +346,9 @@ export async function checkAccountsQuota( // Fetch both Antigravity and Gemini CLI quotas in parallel const [antigravityResponse, geminiCliResponse] = await Promise.all([ - fetchAvailableModels(auth.access ?? "", projectContext.effectiveProjectId) + fetchAvailableModels(auth.access ?? "", projectContext.effectiveProjectId, account.proxies, effectiveAccountIndex) .catch((error): FetchAvailableModelsResponse => ({ models: undefined })), - fetchGeminiCliQuota(auth.access ?? "", projectContext.effectiveProjectId), + fetchGeminiCliQuota(auth.access ?? "", projectContext.effectiveProjectId, account.proxies, effectiveAccountIndex), ]); // Process Antigravity quota @@ -364,7 +371,7 @@ export async function checkAccountsQuota( } results.push({ - index, + index: effectiveAccountIndex, email: account.email, status: "ok", disabled, @@ -376,17 +383,17 @@ export async function checkAccountsQuota( // Log quota status for each family for (const [family, groupQuota] of Object.entries(quotaResult.groups)) { const remainingPercent = (groupQuota.remainingFraction ?? 0) * 100; - logQuotaStatus(account.email, index, remainingPercent, family); + logQuotaStatus(account.email, effectiveAccountIndex, remainingPercent, family); } } catch (error) { results.push({ - index, + index: effectiveAccountIndex, email: account.email, status: "error", disabled, error: error instanceof Error ? error.message : String(error), }); - logQuotaFetch("error", undefined, `account=${account.email ?? index} error=${error instanceof Error ? error.message : String(error)}`); + logQuotaFetch("error", undefined, `account=${account.email ?? effectiveAccountIndex} error=${error instanceof Error ? error.message : String(error)}`); } } diff --git a/src/plugin/refresh-queue.ts b/src/plugin/refresh-queue.ts index 6564ff7..b378272 100644 --- a/src/plugin/refresh-queue.ts +++ b/src/plugin/refresh-queue.ts @@ -224,7 +224,7 @@ export class ProactiveRefreshQueue { minutesUntilExpiry, }); - return refreshAccessToken(auth, this.client, this.providerId); + return refreshAccessToken(auth, this.client, this.providerId, account.proxies, account.index); } /** diff --git a/src/plugin/request.test.ts b/src/plugin/request.test.ts index ee3e866..c797b72 100644 --- a/src/plugin/request.test.ts +++ b/src/plugin/request.test.ts @@ -67,13 +67,36 @@ describe("request.ts", () => { expect(isGenerativeLanguageRequest("https://generativelanguage.googleapis.com/v1/models")).toBe(true); }); - it("returns false for other URLs", () => { - expect(isGenerativeLanguageRequest("https://api.anthropic.com/v1/messages")).toBe(false); + it("returns true for mixed-case host strings", () => { + expect(isGenerativeLanguageRequest("https://GENERATIVELANGUAGE.GOOGLEAPIS.COM/v1/models")).toBe(true); + }); + + it("returns true for Request inputs targeting generativelanguage.googleapis.com", () => { + expect( + isGenerativeLanguageRequest(new Request("https://generativelanguage.googleapis.com/v1beta/models")), + ).toBe(true); + }); + + it("returns true for URL inputs targeting generativelanguage.googleapis.com", () => { + expect( + isGenerativeLanguageRequest(new URL("https://generativelanguage.googleapis.com/v1beta/models")), + ).toBe(true); }); - it("returns false for non-string inputs", () => { - expect(isGenerativeLanguageRequest({} as any)).toBe(false); + it("returns false for non-target hosts", () => { + expect(isGenerativeLanguageRequest("https://api.anthropic.com/v1/messages")).toBe(false); expect(isGenerativeLanguageRequest(new Request("https://example.com"))).toBe(false); + expect(isGenerativeLanguageRequest(new URL("https://example.com"))).toBe(false); + }); + + it("does not match query strings that merely mention the target host", () => { + const disguised = "https://example.com/?next=https://generativelanguage.googleapis.com/v1/models"; + expect(isGenerativeLanguageRequest(disguised)).toBe(false); + expect(isGenerativeLanguageRequest(new Request(disguised))).toBe(false); + }); + + it("returns false for non-url inputs", () => { + expect(isGenerativeLanguageRequest({} as RequestInfo)).toBe(false); }); }); diff --git a/src/plugin/request.ts b/src/plugin/request.ts index 117c7cf..dca06cd 100644 --- a/src/plugin/request.ts +++ b/src/plugin/request.ts @@ -593,8 +593,27 @@ const STREAM_ACTION = "streamGenerateContent"; /** * Detects requests headed to the Google Generative Language API so we can intercept them. */ -export function isGenerativeLanguageRequest(input: RequestInfo): input is string { - return typeof input === "string" && input.includes("generativelanguage.googleapis.com"); +export function isGenerativeLanguageRequest(input: RequestInfo | URL): boolean { + const targetHost = "generativelanguage.googleapis.com"; + + const matchesUrl = (value: string): boolean => { + try { + return new URL(value).hostname.toLowerCase() === targetHost; + } catch { + return value.toLowerCase().includes(targetHost); + } + }; + + if (typeof input === "string") { + return matchesUrl(input); + } + + if (input instanceof URL) { + return input.hostname.toLowerCase() === targetHost; + } + + const candidate = (input as Request).url; + return typeof candidate === "string" && matchesUrl(candidate); } /** @@ -663,7 +682,12 @@ export function prepareAntigravityRequest( headers.delete("x-goog-user-project"); } - const match = input.match(/\/models\/([^:]+):(\w+)/); + const inputUrl = typeof input === "string" + ? input + : input instanceof URL + ? input.toString() + : input.url; + const match = inputUrl.match(/\/models\/([^:]+):(\w+)/); if (!match) { return { request: input, diff --git a/src/plugin/search.ts b/src/plugin/search.ts index cbe53c8..14f88dc 100644 --- a/src/plugin/search.ts +++ b/src/plugin/search.ts @@ -14,6 +14,8 @@ import { SEARCH_SYSTEM_INSTRUCTION, } from "../constants"; import { createLogger } from "./logger"; +import { fetchWithProxy } from "./proxy"; +import type { ProxyConfig } from "./storage"; const log = createLogger("search"); @@ -225,6 +227,8 @@ export async function executeSearch( accessToken: string, projectId: string, abortSignal?: AbortSignal, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { const { query, urls, thinking = true } = args; @@ -281,7 +285,7 @@ export async function executeSearch( }); try { - const response = await fetch(url, { + const response = await fetchWithProxy(url, { method: "POST", headers: { ...getAntigravityHeaders(), @@ -290,7 +294,7 @@ export async function executeSearch( }, body: JSON.stringify(wrappedBody), signal: abortSignal ?? AbortSignal.timeout(SEARCH_TIMEOUT_MS), - }); + }, proxies, accountIndex); if (!response.ok) { const errorText = await response.text(); diff --git a/src/plugin/storage.test.ts b/src/plugin/storage.test.ts index e1c6f9e..e41f84e 100644 --- a/src/plugin/storage.test.ts +++ b/src/plugin/storage.test.ts @@ -429,6 +429,84 @@ describe("Storage Migration", () => { ); expect(gitignoreCall).toBeDefined(); }); + + it("backfills missing proxies arrays for legacy v4 account entries", async () => { + const v4Data = { + version: 4, + accounts: [ + { + refreshToken: "r1", + addedAt: now, + lastUsed: now, + }, + { + refreshToken: "r2", + addedAt: now, + lastUsed: now, + proxies: [{ url: "http://proxy.local:8080" }], + }, + ], + activeIndex: 0, + }; + + vi.mocked(fs.readFile).mockImplementation((path) => { + if ((path as string).endsWith(".gitignore")) { + const error = new Error("ENOENT") as NodeJS.ErrnoException; + error.code = "ENOENT"; + return Promise.reject(error); + } + return Promise.resolve(JSON.stringify(v4Data)); + }); + + const result = await loadAccounts(); + + expect(result).not.toBeNull(); + expect(result?.accounts[0]?.proxies).toEqual([]); + expect(result?.accounts[1]?.proxies).toEqual([{ url: "http://proxy.local:8080/" }]); + + const saveCall = vi.mocked(fs.writeFile).mock.calls.find( + (call) => (call[0] as string).includes(".tmp") + ); + if (!saveCall) throw new Error("saveAccounts was not called for proxy backfill"); + + const savedContent = JSON.parse(saveCall[1] as string); + expect(savedContent.accounts[0].proxies).toEqual([]); + }); + it("normalizes duplicate proxies by URL and keeps the latest entry", async () => { + const v4Data = { + version: 4, + accounts: [ + { + refreshToken: "r1", + addedAt: now, + lastUsed: now, + proxies: [ + { url: "http://proxy.local:8080" }, + { url: "http://proxy.local:8080", enabled: false }, + { url: "http://backup.local:8080" }, + ], + }, + ], + activeIndex: 0, + }; + + vi.mocked(fs.readFile).mockImplementation((path) => { + if ((path as string).endsWith(".gitignore")) { + const error = new Error("ENOENT") as NodeJS.ErrnoException; + error.code = "ENOENT"; + return Promise.reject(error); + } + return Promise.resolve(JSON.stringify(v4Data)); + }); + + const result = await loadAccounts(); + + expect(result).not.toBeNull(); + expect(result?.accounts[0]?.proxies).toEqual([ + { url: "http://proxy.local:8080/", enabled: false }, + { url: "http://backup.local:8080/" }, + ]); + }); }); describe("ensureGitignore", () => { diff --git a/src/plugin/storage.ts b/src/plugin/storage.ts index 58a44c3..1ef9880 100644 --- a/src/plugin/storage.ts +++ b/src/plugin/storage.ts @@ -179,6 +179,11 @@ export interface AccountStorage { export type CooldownReason = "auth-failure" | "network-error" | "project-error" | "validation-required"; +export interface ProxyConfig { + url: string + enabled?: boolean +} + export interface AccountMetadataV3 { email?: string; refreshToken: string; @@ -202,6 +207,8 @@ export interface AccountMetadataV3 { /** Cached soft quota data */ cachedQuota?: Record; cachedQuotaUpdatedAt?: number; + /** Per-account proxy configuration for failover routing */ + proxies?: ProxyConfig[]; } export interface AccountStorageV3 { @@ -230,6 +237,61 @@ type AnyAccountStorage = | AccountStorageV3 | AccountStorageV4; +const SUPPORTED_PROXY_PROTOCOLS = new Set([ + "http:", + "https:", + "socks4:", + "socks4a:", + "socks5:", + "socks5h:", +]); + +function normalizeProxyConfigs(value: unknown): ProxyConfig[] { + if (!Array.isArray(value)) { + return []; + } + + const dedup = new Map(); + + for (const raw of value) { + if (!raw || typeof raw !== "object") { + continue; + } + + const rawUrl = (raw as { url?: unknown }).url; + if (typeof rawUrl !== "string" || !rawUrl.trim()) { + continue; + } + + let parsed: URL; + try { + parsed = new URL(rawUrl); + } catch { + continue; + } + + if (!SUPPORTED_PROXY_PROTOCOLS.has(parsed.protocol.toLowerCase())) { + continue; + } + + const enabledRaw = (raw as { enabled?: unknown }).enabled; + const normalizedUrl = parsed.toString(); + const proxy: ProxyConfig = enabledRaw === false + ? { url: normalizedUrl, enabled: false } + : { url: normalizedUrl }; + dedup.set(proxy.url, proxy); + } + + return [...dedup.values()]; +} + +function normalizeAccountMetadata(account: AccountMetadataV3): AccountMetadataV3 { + return { + ...account, + proxies: normalizeProxyConfigs((account as { proxies?: unknown }).proxies), + }; +} + /** * Gets the legacy Windows config directory (%APPDATA%\opencode). * Used for migration from older plugin versions. @@ -580,6 +642,7 @@ export function migrateV3ToV4(v3: AccountStorageV3): AccountStorageV4 { ...acc, fingerprint: undefined, fingerprintHistory: undefined, + proxies: normalizeProxyConfigs((acc as { proxies?: unknown }).proxies), })), activeIndex: v3.activeIndex, activeIndexByFamily: v3.activeIndexByFamily, @@ -660,6 +723,11 @@ export async function loadAccounts(): Promise { // Deduplicate accounts by email (keeps newest entry for each email) const deduplicatedAccounts = deduplicateAccountsByEmail(validAccounts); + const normalizedAccounts = deduplicatedAccounts.map(normalizeAccountMetadata); + const shouldBackfillProxies = normalizedAccounts.some((_, idx) => { + const original = deduplicatedAccounts[idx] as { proxies?: unknown } | undefined; + return !Array.isArray(original?.proxies); + }); // Clamp activeIndex to valid range after deduplication let activeIndex = @@ -667,16 +735,31 @@ export async function loadAccounts(): Promise { Number.isFinite(storage.activeIndex) ? storage.activeIndex : 0; - if (deduplicatedAccounts.length > 0) { - activeIndex = Math.min(activeIndex, deduplicatedAccounts.length - 1); + if (normalizedAccounts.length > 0) { + activeIndex = Math.min(activeIndex, normalizedAccounts.length - 1); activeIndex = Math.max(activeIndex, 0); } else { activeIndex = 0; } + if (shouldBackfillProxies) { + try { + await saveAccounts({ + version: 4, + accounts: normalizedAccounts, + activeIndex, + activeIndexByFamily: storage.activeIndexByFamily, + }); + } catch (saveError) { + log.warn("Failed to persist proxy backfill", { + error: String(saveError), + }); + } + } + return { version: 4, - accounts: deduplicatedAccounts, + accounts: normalizedAccounts, activeIndex, activeIndexByFamily: storage.activeIndexByFamily, }; @@ -754,21 +837,33 @@ async function loadAccountsUnsafe(): Promise { await ensureSecurePermissions(path); const content = await fs.readFile(path, "utf-8"); - const parsed = JSON.parse(content); + const parsed = JSON.parse(content) as AnyAccountStorage; if (parsed.version === 1) { - return migrateV3ToV4(migrateV2ToV3(migrateV1ToV2(parsed))); + const migrated = migrateV3ToV4(migrateV2ToV3(migrateV1ToV2(parsed))); + return { + ...migrated, + accounts: deduplicateAccountsByEmail(migrated.accounts).map(normalizeAccountMetadata), + }; } if (parsed.version === 2) { - return migrateV3ToV4(migrateV2ToV3(parsed)); + const migrated = migrateV3ToV4(migrateV2ToV3(parsed)); + return { + ...migrated, + accounts: deduplicateAccountsByEmail(migrated.accounts).map(normalizeAccountMetadata), + }; } if (parsed.version === 3) { - return migrateV3ToV4(parsed); + const migrated = migrateV3ToV4(parsed); + return { + ...migrated, + accounts: deduplicateAccountsByEmail(migrated.accounts).map(normalizeAccountMetadata), + }; } return { ...parsed, - accounts: deduplicateAccountsByEmail(parsed.accounts), + accounts: deduplicateAccountsByEmail(parsed.accounts).map(normalizeAccountMetadata), }; } catch (error) { const code = (error as NodeJS.ErrnoException).code; diff --git a/src/plugin/token-proxy.test.ts b/src/plugin/token-proxy.test.ts new file mode 100644 index 0000000..e0e7278 --- /dev/null +++ b/src/plugin/token-proxy.test.ts @@ -0,0 +1,65 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" + +const { fetchWithProxyMock } = vi.hoisted(() => ({ + fetchWithProxyMock: vi.fn(), +})) + +vi.mock("./proxy", () => ({ + fetchWithProxy: fetchWithProxyMock, +})) + +import { ANTIGRAVITY_PROVIDER_ID } from "../constants" +import { refreshAccessToken } from "./token" +import type { OAuthAuthDetails, PluginClient } from "./types" + +function createClient(): PluginClient { + return { + auth: { + set: vi.fn(async () => {}), + }, + } as unknown as PluginClient +} + +describe("refreshAccessToken proxy routing", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("passes account proxies and account index to fetchWithProxy", async () => { + fetchWithProxyMock.mockResolvedValueOnce( + new Response( + JSON.stringify({ + access_token: "access-next", + expires_in: 3600, + }), + { status: 200 }, + ), + ) + + const auth: OAuthAuthDetails = { + type: "oauth", + refresh: "refresh-token|project-1", + access: "access-old", + expires: Date.now() - 1000, + } + const proxies = [{ url: "http://127.0.0.1:8080" }] + const accountIndex = 11 + + const result = await refreshAccessToken( + auth, + createClient(), + ANTIGRAVITY_PROVIDER_ID, + proxies, + accountIndex, + ) + + expect(result?.access).toBe("access-next") + expect(fetchWithProxyMock).toHaveBeenCalledTimes(1) + + const [url, init, passedProxies, passedAccountIndex] = fetchWithProxyMock.mock.calls[0] ?? [] + expect(url).toBe("https://oauth2.googleapis.com/token") + expect((init as RequestInit | undefined)?.method).toBe("POST") + expect(passedProxies).toEqual(proxies) + expect(passedAccountIndex).toBe(accountIndex) + }) +}) \ No newline at end of file diff --git a/src/plugin/token.ts b/src/plugin/token.ts index b2b1c89..7da053e 100644 --- a/src/plugin/token.ts +++ b/src/plugin/token.ts @@ -3,7 +3,9 @@ import { formatRefreshParts, parseRefreshParts, calculateTokenExpiry } from "./a import { clearCachedAuth, storeCachedAuth } from "./cache"; import { createLogger } from "./logger"; import { invalidateProjectContextCache } from "./project"; +import { fetchWithProxy } from "./proxy"; import type { OAuthAuthDetails, PluginClient, RefreshParts } from "./types"; +import type { ProxyConfig } from "./storage"; const log = createLogger("token"); @@ -86,6 +88,8 @@ export async function refreshAccessToken( auth: OAuthAuthDetails, client: PluginClient, providerId: string, + proxies?: ProxyConfig[], + accountIndex?: number, ): Promise { const parts = parseRefreshParts(auth.refresh); if (!parts.refreshToken) { @@ -94,7 +98,7 @@ export async function refreshAccessToken( try { const startTime = Date.now(); - const response = await fetch("https://oauth2.googleapis.com/token", { + const response = await fetchWithProxy("https://oauth2.googleapis.com/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", @@ -105,7 +109,7 @@ export async function refreshAccessToken( client_id: ANTIGRAVITY_CLIENT_ID, client_secret: ANTIGRAVITY_CLIENT_SECRET, }), - }); + }, proxies, accountIndex); if (!response.ok) { let errorText: string | undefined; diff --git a/src/plugin/ui/auth-menu.ts b/src/plugin/ui/auth-menu.ts index 6e3ec88..fd5fe46 100644 --- a/src/plugin/ui/auth-menu.ts +++ b/src/plugin/ui/auth-menu.ts @@ -24,7 +24,7 @@ export type AuthMenuAction = | { type: 'configure-models' } | { type: 'cancel' }; -export type AccountAction = 'back' | 'delete' | 'refresh' | 'toggle' | 'verify' | 'cancel'; +export type AccountAction = 'back' | 'delete' | 'refresh' | 'toggle' | 'proxy' | 'verify' | 'cancel'; function formatRelativeTime(timestamp: number | undefined): string { if (!timestamp) return 'never'; @@ -118,6 +118,7 @@ export async function showAccountDetails(account: AccountInfo): Promise