diff --git a/.changeset/chilly-bugs-pay.md b/.changeset/chilly-bugs-pay.md deleted file mode 100644 index b30f8241ef..0000000000 --- a/.changeset/chilly-bugs-pay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Delete task confirmation enhancements diff --git a/.changeset/healthy-buckets-attack.md b/.changeset/healthy-buckets-attack.md new file mode 100644 index 0000000000..8961d7151e --- /dev/null +++ b/.changeset/healthy-buckets-attack.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +ExtensionStateContext does not correctly merge state diff --git a/.changeset/weak-cameras-hope.md b/.changeset/weak-cameras-hope.md new file mode 100644 index 0000000000..62eecb7fe0 --- /dev/null +++ b/.changeset/weak-cameras-hope.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Default middle-out compression to on for OpenRouter diff --git a/.changeset/young-hornets-taste.md b/.changeset/young-hornets-taste.md deleted file mode 100644 index 1b9c3d94e8..0000000000 --- a/.changeset/young-hornets-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Prettier thinking blocks diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7ee8bb98ad..de7e461cb9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,37 +1,35 @@ - +## Context -## Description + -## Type of change +## Implementation - + - +## Screenshots -## Checklist: +| before | after | +| ------ | ----- | +| | | - +## How to Test -- [ ] My code follows the patterns of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation + +A "How To Test" section can look something like this: -## Related Issues +- Sign in with a user with tracks +- Activate `show_awesome_cat_gifs` feature (add `?feature.show_awesome_cat_gifs=1` to your URL) +- You should see a GIF with cats dancing - +--> -## Reviewers +## Get in Touch - + diff --git a/.github/workflows/changeset-release.yml b/.github/workflows/changeset-release.yml index a2bcd3f039..462516365b 100644 --- a/.github/workflows/changeset-release.yml +++ b/.github/workflows/changeset-release.yml @@ -37,7 +37,7 @@ jobs: cache: 'npm' - name: Install Dependencies - run: npm run install:all + run: npm run install:ci # Check if there are any new changesets to process - name: Check for changesets diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index fde891f804..b7292dd9ee 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -20,7 +20,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Compile run: npm run compile - name: Check types @@ -39,7 +39,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Run knip checks run: npm run knip @@ -54,7 +54,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Run unit tests run: npx jest --silent @@ -69,7 +69,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Run unit tests working-directory: webview-ui run: npx jest --silent @@ -108,9 +108,11 @@ jobs: with: node-version: '18' cache: 'npm' - - name: Create env.integration file - run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration - name: Install dependencies - run: npm run install:all + run: npm run install:ci + - name: Create env.integration file + working-directory: e2e + run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration - name: Run integration tests - run: xvfb-run -a npm run test:integration + working-directory: e2e + run: xvfb-run -a npm run ci diff --git a/.github/workflows/marketplace-publish.yml b/.github/workflows/marketplace-publish.yml index fcc089c1db..794e598b80 100644 --- a/.github/workflows/marketplace-publish.yml +++ b/.github/workflows/marketplace-publish.yml @@ -29,10 +29,7 @@ jobs: - name: Install Dependencies run: | npm install -g vsce ovsx - npm install - cd webview-ui - npm install - cd .. + npm run install:ci - name: Package and Publish Extension env: VSCE_PAT: ${{ secrets.VSCE_PAT }} diff --git a/.vscodeignore b/.vscodeignore index 638ac22db7..1fc5a728b0 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -4,6 +4,8 @@ .vscode/** .vscode-test/** out/** +out-integration/** +e2e/** node_modules/** src/** .gitignore @@ -25,7 +27,6 @@ demo.gif .roomodes cline_docs/** coverage/** -out-integration/** # Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore) webview-ui/src/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 9622ce0c99..c4be3d8838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Roo Code Changelog +## [3.7.11] + +- Don't honor custom max tokens for non thinking models +- Include custom modes in mode switching keyboard shortcut +- Support read-only modes that can run commands + +## [3.7.10] + +- Add Gemini models on Vertex AI (thanks @ashktn!) +- Keyboard shortcuts to switch modes (thanks @aheizi!) +- Add support for Mermaid diagrams (thanks Cline!) + +## [3.7.9] + +- Delete task confirmation enhancements +- Smarter context window management +- Prettier thinking blocks +- Fix maxTokens defaults for Claude 3.7 Sonnet models +- Terminal output parsing improvements (thanks @KJ7LNW!) +- UI fix to dropdown hover colors (thanks @SamirSaji!) +- Add support for Claude Sonnet 3.7 thinking via Vertex AI (thanks @lupuletic!) + ## [3.7.8] - Add Vertex AI prompt caching support for Claude models (thanks @aitoroses and @lupuletic!) diff --git a/README.md b/README.md index 60161110e2..94abdbf087 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,12 @@ Make Roo Code work your way with: ```bash npm run install:all ``` + +if that fails, try: + ```bash + npm run install:ci + ``` + 3. **Build** the extension: ```bash npm run build diff --git a/.env.integration.example b/e2e/.env.integration.example similarity index 100% rename from .env.integration.example rename to e2e/.env.integration.example diff --git a/.vscode-test.mjs b/e2e/.vscode-test.mjs similarity index 89% rename from .vscode-test.mjs rename to e2e/.vscode-test.mjs index dd7760789b..ccc8b495ea 100644 --- a/.vscode-test.mjs +++ b/e2e/.vscode-test.mjs @@ -6,7 +6,7 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ label: 'integrationTest', - files: 'out-integration/test/**/*.test.js', + files: 'out/suite/**/*.test.js', workspaceFolder: '.', mocha: { ui: 'tdd', diff --git a/src/test/VSCODE_INTEGRATION_TESTS.md b/e2e/VSCODE_INTEGRATION_TESTS.md similarity index 98% rename from src/test/VSCODE_INTEGRATION_TESTS.md rename to e2e/VSCODE_INTEGRATION_TESTS.md index f5882fea1e..25f54492de 100644 --- a/src/test/VSCODE_INTEGRATION_TESTS.md +++ b/e2e/VSCODE_INTEGRATION_TESTS.md @@ -11,8 +11,8 @@ The integration tests use the `@vscode/test-electron` package to run tests in a ### Directory Structure ``` -src/test/ -├── runTest.ts # Main test runner +e2e/src/ +├── runTest.ts # Main test runner ├── suite/ │ ├── index.ts # Test suite configuration │ ├── modes.test.ts # Mode switching tests diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 0000000000..278df120c2 --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,2387 @@ +{ + "name": "e2e", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "e2e", + "version": "0.1.0", + "devDependencies": { + "@types/mocha": "^10.0.10", + "@vscode/test-cli": "^0.0.9", + "@vscode/test-electron": "^2.4.0", + "mocha": "^11.1.0", + "typescript": "^5.4.5" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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": ">=12" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/test-cli": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.9.tgz", + "integrity": "sha512-vsl5/ueE3Jf0f6XzB0ECHHMsd5A0Yu6StElb8a+XsubZW7kHNAOw4Y3TSSuDzKEpLnJ92nbMy1Zl+KLGCE6NaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mocha": "^10.0.2", + "c8": "^9.1.0", + "chokidar": "^3.5.3", + "enhanced-resolve": "^5.15.0", + "glob": "^10.3.10", + "minimatch": "^9.0.3", + "mocha": "^10.2.0", + "supports-color": "^9.4.0", + "yargs": "^17.7.2" + }, + "bin": { + "vscode-test": "out/bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vscode/test-cli/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": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@vscode/test-cli/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/@vscode/test-cli/node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/node_modules/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": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@vscode/test-cli/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-electron": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", + "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^7.0.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/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": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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": { + "node": ">=8" + } + }, + "node_modules/cliui/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/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/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": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "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": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "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/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "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": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "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/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "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": { + "node": ">=8" + } + }, + "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": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", + "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", + "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.3.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "string-width": "^6.1.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "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/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "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": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "node": ">=8" + } + }, + "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" + } + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "node": ">=8" + } + }, + "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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "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": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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": { + "node": ">=8" + } + }, + "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": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/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": { + "node": ">=8" + } + }, + "node_modules/yargs/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/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000000..630a13f0e7 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,21 @@ +{ + "name": "e2e", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "cd .. && npm run build", + "compile": "tsc -p tsconfig.json", + "lint": "eslint src --ext ts", + "check-types": "tsc --noEmit", + "test": "npm run compile && npx dotenvx run -f .env.integration -- node ./out/runTest.js", + "ci": "npm run build && npm run test" + }, + "dependencies": {}, + "devDependencies": { + "@types/mocha": "^10.0.10", + "@vscode/test-cli": "^0.0.9", + "@vscode/test-electron": "^2.4.0", + "mocha": "^11.1.0", + "typescript": "^5.4.5" + } +} diff --git a/src/test/runTest.ts b/e2e/src/runTest.ts similarity index 100% rename from src/test/runTest.ts rename to e2e/src/runTest.ts diff --git a/src/test/suite/extension.test.ts b/e2e/src/suite/extension.test.ts similarity index 100% rename from src/test/suite/extension.test.ts rename to e2e/src/suite/extension.test.ts diff --git a/src/test/suite/index.ts b/e2e/src/suite/index.ts similarity index 95% rename from src/test/suite/index.ts rename to e2e/src/suite/index.ts index cc487b0bf7..a9540d9600 100644 --- a/src/test/suite/index.ts +++ b/e2e/src/suite/index.ts @@ -1,8 +1,7 @@ import * as path from "path" import Mocha from "mocha" import { glob } from "glob" -import { ClineAPI } from "../../exports/cline" -import { ClineProvider } from "../../core/webview/ClineProvider" +import { ClineAPI, ClineProvider } from "../../../src/exports/cline" import * as vscode from "vscode" declare global { diff --git a/src/test/suite/modes.test.ts b/e2e/src/suite/modes.test.ts similarity index 100% rename from src/test/suite/modes.test.ts rename to e2e/src/suite/modes.test.ts diff --git a/src/test/suite/task.test.ts b/e2e/src/suite/task.test.ts similarity index 100% rename from src/test/suite/task.test.ts rename to e2e/src/suite/task.test.ts diff --git a/tsconfig.integration.json b/e2e/tsconfig.json similarity index 60% rename from tsconfig.integration.json rename to e2e/tsconfig.json index 0de0ea736a..792acb14a0 100644 --- a/tsconfig.integration.json +++ b/e2e/tsconfig.json @@ -9,9 +9,8 @@ "strict": true, "skipLibCheck": true, "useUnknownInCatchVariables": false, - "rootDir": "src", - "outDir": "out-integration" + "outDir": "out" }, - "include": ["**/*.ts"], - "exclude": [".vscode-test", "benchmark", "dist", "**/node_modules/**", "out", "out-integration", "webview-ui"] + "include": ["src", "../src/exports/cline.d.ts"], + "exclude": [".vscode-test", "**/node_modules/**", "out"] } diff --git a/knip.json b/knip.json index b0e0839da7..a9f0b93e0d 100644 --- a/knip.json +++ b/knip.json @@ -16,7 +16,9 @@ "src/activate/**", "src/exports/**", "src/extension.ts", - ".vscode-test.mjs" + "e2e/.vscode-test.mjs", + "e2e/src/runTest.ts", + "e2e/src/suite/index.ts" ], "workspaces": { "webview-ui": { diff --git a/package-lock.json b/package-lock.json index 950769b39b..0e3e29d44e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "roo-cline", - "version": "3.7.8", + "version": "3.7.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.7.8", + "version": "3.7.11", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.706.0", + "@google-cloud/vertexai": "^1.9.3", "@google/generative-ai": "^0.18.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", @@ -34,6 +35,7 @@ "get-folder-size": "^5.0.0", "globby": "^14.0.2", "isbinaryfile": "^5.0.2", + "js-tiktoken": "^1.0.19", "mammoth": "^1.8.0", "monaco-vscode-textmate-theme-converter": "^0.1.7", "openai": "^4.78.1", @@ -63,13 +65,10 @@ "@types/diff-match-patch": "^1.0.36", "@types/glob": "^8.1.0", "@types/jest": "^29.5.14", - "@types/mocha": "^10.0.10", "@types/node": "20.x", "@types/string-similarity": "^4.0.2", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", - "@vscode/test-cli": "^0.0.9", - "@vscode/test-electron": "^2.4.0", "esbuild": "^0.24.0", "eslint": "^8.57.0", "glob": "^11.0.1", @@ -79,7 +78,6 @@ "knip": "^5.44.4", "lint-staged": "^15.2.11", "mkdirp": "^3.0.1", - "mocha": "^11.1.0", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "rimraf": "^6.0.1", @@ -3241,6 +3239,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@google-cloud/vertexai": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@google-cloud/vertexai/-/vertexai-1.9.3.tgz", + "integrity": "sha512-35o5tIEMLW3JeFJOaaMNR2e5sq+6rpnhrF97PuAxeOm0GlqVTESKhkGj7a5B5mmJSSSU3hUfIhcQCRRsw4Ipzg==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@google/generative-ai": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.18.0.tgz", @@ -4153,17 +4163,6 @@ "node": ">= 8" } }, - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@puppeteer/browsers": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", @@ -6032,13 +6031,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -6351,332 +6343,6 @@ "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.36.tgz", "integrity": "sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==" }, - "node_modules/@vscode/test-cli": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.9.tgz", - "integrity": "sha512-vsl5/ueE3Jf0f6XzB0ECHHMsd5A0Yu6StElb8a+XsubZW7kHNAOw4Y3TSSuDzKEpLnJ92nbMy1Zl+KLGCE6NaA==", - "dev": true, - "dependencies": { - "@types/mocha": "^10.0.2", - "c8": "^9.1.0", - "chokidar": "^3.5.3", - "enhanced-resolve": "^5.15.0", - "glob": "^10.3.10", - "minimatch": "^9.0.3", - "mocha": "^10.2.0", - "supports-color": "^9.4.0", - "yargs": "^17.7.2" - }, - "bin": { - "vscode-test": "out/bin.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vscode/test-cli/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": { - "node": ">=8" - } - }, - "node_modules/@vscode/test-cli/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@vscode/test-cli/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/@vscode/test-cli/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/@vscode/test-cli/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-cli/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": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/@vscode/test-cli/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/@vscode/test-cli/node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vscode/test-cli/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==", - "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/@vscode/test-cli/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@vscode/test-cli/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@vscode/test-cli/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": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@vscode/test-cli/node_modules/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": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@vscode/test-cli/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@vscode/test-electron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", - "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", - "dev": true, - "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "jszip": "^3.10.1", - "ora": "^7.0.1", - "semver": "^7.6.2" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -7179,43 +6845,6 @@ "node": "*" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", @@ -7251,12 +6880,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "node_modules/browserslist": { "version": "4.24.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", @@ -7310,30 +6933,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -7361,31 +6960,6 @@ "node": ">= 0.8" } }, - "node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=14.14.0" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -7595,33 +7169,6 @@ "node": ">=6" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -7980,18 +7527,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -9197,15 +8732,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -9863,15 +9389,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -10169,18 +9686,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.0.tgz", @@ -10320,18 +9825,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -10389,15 +9882,6 @@ "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -10525,18 +10009,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -11451,6 +10923,14 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tiktoken": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.19.tgz", + "integrity": "sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12077,22 +11557,6 @@ "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", "dev": true }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", @@ -12480,183 +11944,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/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": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/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": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/mocha/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/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/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==", - "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/mocha/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/monaco-vscode-textmate-theme-converter": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/monaco-vscode-textmate-theme-converter/-/monaco-vscode-textmate-theme-converter-0.1.7.tgz", @@ -12801,6 +12088,7 @@ "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", @@ -13136,92 +12424,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "dev": true, - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true - }, - "node_modules/ora/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dev": true, - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/os-name": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/os-name/-/os-name-6.0.0.tgz", @@ -13895,15 +13097,6 @@ "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/raw-body": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", @@ -14152,28 +13345,6 @@ "node": ">=10" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/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==", - "dev": true - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14409,15 +13580,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -14750,21 +13912,6 @@ "node": ">= 0.8" } }, - "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "dev": true, - "dependencies": { - "bl": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/streamx": { "version": "2.21.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.0.tgz", @@ -15051,18 +14198,6 @@ "integrity": "sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==", "dev": true }, - "node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -15966,12 +15101,6 @@ "node": ">=0.10.0" } }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -16170,33 +15299,6 @@ "node": ">=12" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", diff --git a/package.json b/package.json index fe64af16d6..5e75c57ad4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Code (prev. Roo Cline)", "description": "A whole dev team of AI agents in your editor.", "publisher": "RooVeterinaryInc", - "version": "3.7.8", + "version": "3.7.11", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", @@ -276,21 +276,24 @@ "scripts": { "build": "npm run build:webview && npm run vsix", "build:webview": "cd webview-ui && npm run build", - "changeset": "changeset", - "check-types": "tsc --noEmit && cd webview-ui && npm run check-types", "compile": "tsc -p . --outDir out && node esbuild.js", - "compile:integration": "tsc -p tsconfig.integration.json", - "install:all": "npm install && cd webview-ui && npm install", - "knip": "knip --include files", - "lint": "eslint src --ext ts && npm run lint --prefix webview-ui", - "lint-local": "eslint -c .eslintrc.local.json src --ext ts && npm run lint --prefix webview-ui", - "lint-fix": "eslint src --ext ts --fix && npm run lint-fix --prefix webview-ui", - "lint-fix-local": "eslint -c .eslintrc.local.json src --ext ts --fix && npm run lint-fix --prefix webview-ui", + "install:all": "npm-run-all -p install-*", + "install:ci": "npm install npm-run-all && npm run install:all", + "install-extension": "npm install", + "install-webview-ui": "cd webview-ui && npm install", + "install-e2e": "cd e2e && npm install", + "lint": "npm-run-all -p lint:*", + "lint:extension": "eslint src --ext ts", + "lint:webview-ui": "cd webview-ui && npm run lint", + "lint:e2e": "cd e2e && npm run lint", + "check-types": "npm-run-all -p check-types:*", + "check-types:extension": "tsc --noEmit", + "check-types:webview-ui": "cd webview-ui && npm run check-types", + "check-types:e2e": "cd e2e && npm run check-types", "package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production", - "pretest": "npm run compile && npm run compile:integration", + "pretest": "npm run compile", "dev": "cd webview-ui && npm run dev", "test": "jest && cd webview-ui && npm run test", - "test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- node ./out-integration/test/runTest.js", "prepare": "husky", "publish:marketplace": "vsce publish && ovsx publish", "publish": "npm run build && changeset publish && npm install --package-lock-only", @@ -300,7 +303,9 @@ "watch": "npm-run-all -p watch:*", "watch:esbuild": "node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", - "watch-tests": "tsc -p . -w --outDir out" + "watch-tests": "tsc -p . -w --outDir out", + "changeset": "changeset", + "knip": "knip --include files" }, "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", @@ -308,6 +313,7 @@ "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.706.0", "@google/generative-ai": "^0.18.0", + "@google-cloud/vertexai": "^1.9.3", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", "@types/clone-deep": "^4.0.4", @@ -329,6 +335,7 @@ "get-folder-size": "^5.0.0", "globby": "^14.0.2", "isbinaryfile": "^5.0.2", + "js-tiktoken": "^1.0.19", "mammoth": "^1.8.0", "monaco-vscode-textmate-theme-converter": "^0.1.7", "openai": "^4.78.1", @@ -358,13 +365,10 @@ "@types/diff-match-patch": "^1.0.36", "@types/glob": "^8.1.0", "@types/jest": "^29.5.14", - "@types/mocha": "^10.0.10", "@types/node": "20.x", "@types/string-similarity": "^4.0.2", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", - "@vscode/test-cli": "^0.0.9", - "@vscode/test-electron": "^2.4.0", "esbuild": "^0.24.0", "eslint": "^8.57.0", "glob": "^11.0.1", @@ -374,7 +378,6 @@ "knip": "^5.44.4", "lint-staged": "^15.2.11", "mkdirp": "^3.0.1", - "mocha": "^11.1.0", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "rimraf": "^6.0.1", diff --git a/src/api/__tests__/index.test.ts b/src/api/__tests__/index.test.ts new file mode 100644 index 0000000000..4408ca0ffc --- /dev/null +++ b/src/api/__tests__/index.test.ts @@ -0,0 +1,257 @@ +// npx jest src/api/__tests__/index.test.ts + +import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta/messages/index.mjs" + +import { getModelParams } from "../index" +import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "../providers/constants" + +describe("getModelParams", () => { + it("should return default values when no custom values are provided", () => { + const options = {} + const model = { + id: "test-model", + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + defaultMaxTokens: 1000, + defaultTemperature: 0.5, + }) + + expect(result).toEqual({ + maxTokens: 1000, + thinking: undefined, + temperature: 0.5, + }) + }) + + it("should use custom temperature from options when provided", () => { + const options = { modelTemperature: 0.7 } + const model = { + id: "test-model", + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + defaultMaxTokens: 1000, + defaultTemperature: 0.5, + }) + + expect(result).toEqual({ + maxTokens: 1000, + thinking: undefined, + temperature: 0.7, + }) + }) + + it("should use model maxTokens when available", () => { + const options = {} + const model = { + id: "test-model", + maxTokens: 2000, + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + defaultMaxTokens: 1000, + }) + + expect(result).toEqual({ + maxTokens: 2000, + thinking: undefined, + temperature: 0, + }) + }) + + it("should handle thinking models correctly", () => { + const options = {} + const model = { + id: "test-model", + thinking: true, + maxTokens: 2000, + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + }) + + const expectedThinking: BetaThinkingConfigParam = { + type: "enabled", + budget_tokens: 1600, // 80% of 2000 + } + + expect(result).toEqual({ + maxTokens: 2000, + thinking: expectedThinking, + temperature: 1.0, // Thinking models require temperature 1.0. + }) + }) + + it("should honor customMaxTokens for thinking models", () => { + const options = { modelMaxTokens: 3000 } + const model = { + id: "test-model", + thinking: true, + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + defaultMaxTokens: 2000, + }) + + const expectedThinking: BetaThinkingConfigParam = { + type: "enabled", + budget_tokens: 2400, // 80% of 3000 + } + + expect(result).toEqual({ + maxTokens: 3000, + thinking: expectedThinking, + temperature: 1.0, + }) + }) + + it("should honor customMaxThinkingTokens for thinking models", () => { + const options = { modelMaxThinkingTokens: 1500 } + const model = { + id: "test-model", + thinking: true, + maxTokens: 4000, + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + }) + + const expectedThinking: BetaThinkingConfigParam = { + type: "enabled", + budget_tokens: 1500, // Using the custom value + } + + expect(result).toEqual({ + maxTokens: 4000, + thinking: expectedThinking, + temperature: 1.0, + }) + }) + + it("should not honor customMaxThinkingTokens for non-thinking models", () => { + const options = { modelMaxThinkingTokens: 1500 } + const model = { + id: "test-model", + maxTokens: 4000, + contextWindow: 16000, + supportsPromptCache: true, + // Note: model.thinking is not set (so it's falsey). + } + + const result = getModelParams({ + options, + model, + }) + + expect(result).toEqual({ + maxTokens: 4000, + thinking: undefined, // Should remain undefined despite customMaxThinkingTokens being set. + temperature: 0, // Using default temperature. + }) + }) + + it("should clamp thinking budget to at least 1024 tokens", () => { + const options = { modelMaxThinkingTokens: 500 } + const model = { + id: "test-model", + thinking: true, + maxTokens: 2000, + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + }) + + const expectedThinking: BetaThinkingConfigParam = { + type: "enabled", + budget_tokens: 1024, // Minimum is 1024 + } + + expect(result).toEqual({ + maxTokens: 2000, + thinking: expectedThinking, + temperature: 1.0, + }) + }) + + it("should clamp thinking budget to at most 80% of max tokens", () => { + const options = { modelMaxThinkingTokens: 5000 } + const model = { + id: "test-model", + thinking: true, + maxTokens: 4000, + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + }) + + const expectedThinking: BetaThinkingConfigParam = { + type: "enabled", + budget_tokens: 3200, // 80% of 4000 + } + + expect(result).toEqual({ + maxTokens: 4000, + thinking: expectedThinking, + temperature: 1.0, + }) + }) + + it("should use ANTHROPIC_DEFAULT_MAX_TOKENS when no maxTokens is provided for thinking models", () => { + const options = {} + const model = { + id: "test-model", + thinking: true, + contextWindow: 16000, + supportsPromptCache: true, + } + + const result = getModelParams({ + options, + model, + }) + + const expectedThinking: BetaThinkingConfigParam = { + type: "enabled", + budget_tokens: Math.floor(ANTHROPIC_DEFAULT_MAX_TOKENS * 0.8), + } + + expect(result).toEqual({ + maxTokens: undefined, + thinking: expectedThinking, + temperature: 1.0, + }) + }) +}) diff --git a/src/api/index.ts b/src/api/index.ts index 85572632ec..cf8085e289 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,6 +1,9 @@ import { Anthropic } from "@anthropic-ai/sdk" +import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta/messages/index.mjs" + +import { ApiConfiguration, ModelInfo, ApiHandlerOptions } from "../shared/api" +import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "./providers/constants" import { GlamaHandler } from "./providers/glama" -import { ApiConfiguration, ModelInfo } from "../shared/api" import { AnthropicHandler } from "./providers/anthropic" import { AwsBedrockHandler } from "./providers/bedrock" import { OpenRouterHandler } from "./providers/openrouter" @@ -25,6 +28,16 @@ export interface SingleCompletionHandler { export interface ApiHandler { createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream getModel(): { id: string; info: ModelInfo } + + /** + * Counts tokens for content blocks + * All providers extend BaseProvider which provides a default tiktoken implementation, + * but they can override this to use their native token counting endpoints + * + * @param content The content to count tokens for + * @returns A promise resolving to the token count + */ + countTokens(content: Array): Promise } export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { @@ -66,3 +79,41 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { return new AnthropicHandler(options) } } + +export function getModelParams({ + options, + model, + defaultMaxTokens, + defaultTemperature = 0, +}: { + options: ApiHandlerOptions + model: ModelInfo + defaultMaxTokens?: number + defaultTemperature?: number +}) { + const { + modelMaxTokens: customMaxTokens, + modelMaxThinkingTokens: customMaxThinkingTokens, + modelTemperature: customTemperature, + } = options + + let maxTokens = model.maxTokens ?? defaultMaxTokens + let thinking: BetaThinkingConfigParam | undefined = undefined + let temperature = customTemperature ?? defaultTemperature + + if (model.thinking) { + // Only honor `customMaxTokens` for thinking models. + maxTokens = customMaxTokens ?? maxTokens + + // Clamp the thinking budget to be at most 80% of max tokens and at + // least 1024 tokens. + const maxBudgetTokens = Math.floor((maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS) * 0.8) + const budgetTokens = Math.max(Math.min(customMaxThinkingTokens ?? maxBudgetTokens, maxBudgetTokens), 1024) + thinking = { type: "enabled", budget_tokens: budgetTokens } + + // Anthropic "Thinking" models require a temperature of 1.0. + temperature = 1.0 + } + + return { maxTokens, thinking, temperature } +} diff --git a/src/api/providers/__tests__/anthropic.test.ts b/src/api/providers/__tests__/anthropic.test.ts index 82e098f65f..acea77f315 100644 --- a/src/api/providers/__tests__/anthropic.test.ts +++ b/src/api/providers/__tests__/anthropic.test.ts @@ -194,5 +194,33 @@ describe("AnthropicHandler", () => { expect(model.info.supportsImages).toBe(true) expect(model.info.supportsPromptCache).toBe(true) }) + + it("honors custom maxTokens for thinking models", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-3-7-sonnet-20250219:thinking", + modelMaxTokens: 32_768, + modelMaxThinkingTokens: 16_384, + }) + + const result = handler.getModel() + expect(result.maxTokens).toBe(32_768) + expect(result.thinking).toEqual({ type: "enabled", budget_tokens: 16_384 }) + expect(result.temperature).toBe(1.0) + }) + + it("does not honor custom maxTokens for non-thinking models", () => { + const handler = new AnthropicHandler({ + apiKey: "test-api-key", + apiModelId: "claude-3-7-sonnet-20250219", + modelMaxTokens: 32_768, + modelMaxThinkingTokens: 16_384, + }) + + const result = handler.getModel() + expect(result.maxTokens).toBe(16_384) + expect(result.thinking).toBeUndefined() + expect(result.temperature).toBe(0) + }) }) }) diff --git a/src/api/providers/__tests__/openrouter.test.ts b/src/api/providers/__tests__/openrouter.test.ts index aabd7f71a8..892c138172 100644 --- a/src/api/providers/__tests__/openrouter.test.ts +++ b/src/api/providers/__tests__/openrouter.test.ts @@ -1,29 +1,30 @@ // npx jest src/api/providers/__tests__/openrouter.test.ts +import axios from "axios" +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" + import { OpenRouterHandler } from "../openrouter" import { ApiHandlerOptions, ModelInfo } from "../../../shared/api" -import OpenAI from "openai" -import axios from "axios" -import { Anthropic } from "@anthropic-ai/sdk" // Mock dependencies jest.mock("openai") jest.mock("axios") jest.mock("delay", () => jest.fn(() => Promise.resolve())) +const mockOpenRouterModelInfo: ModelInfo = { + maxTokens: 1000, + contextWindow: 2000, + supportsPromptCache: true, + inputPrice: 0.01, + outputPrice: 0.02, +} + describe("OpenRouterHandler", () => { const mockOptions: ApiHandlerOptions = { openRouterApiKey: "test-key", openRouterModelId: "test-model", - openRouterModelInfo: { - name: "Test Model", - description: "Test Description", - maxTokens: 1000, - contextWindow: 2000, - supportsPromptCache: true, - inputPrice: 0.01, - outputPrice: 0.02, - } as ModelInfo, + openRouterModelInfo: mockOpenRouterModelInfo, } beforeEach(() => { @@ -50,6 +51,10 @@ describe("OpenRouterHandler", () => { expect(result).toEqual({ id: mockOptions.openRouterModelId, info: mockOptions.openRouterModelInfo, + maxTokens: 1000, + temperature: 0, + thinking: undefined, + topP: undefined, }) }) @@ -61,6 +66,38 @@ describe("OpenRouterHandler", () => { expect(result.info.supportsPromptCache).toBe(true) }) + test("getModel honors custom maxTokens for thinking models", () => { + const handler = new OpenRouterHandler({ + openRouterApiKey: "test-key", + openRouterModelId: "test-model", + openRouterModelInfo: { + ...mockOpenRouterModelInfo, + maxTokens: 64_000, + thinking: true, + }, + modelMaxTokens: 32_768, + modelMaxThinkingTokens: 16_384, + }) + + const result = handler.getModel() + expect(result.maxTokens).toBe(32_768) + expect(result.thinking).toEqual({ type: "enabled", budget_tokens: 16_384 }) + expect(result.temperature).toBe(1.0) + }) + + test("getModel does not honor custom maxTokens for non-thinking models", () => { + const handler = new OpenRouterHandler({ + ...mockOptions, + modelMaxTokens: 32_768, + modelMaxThinkingTokens: 16_384, + }) + + const result = handler.getModel() + expect(result.maxTokens).toBe(1000) + expect(result.thinking).toBeUndefined() + expect(result.temperature).toBe(0) + }) + test("createMessage generates correct stream chunks", async () => { const handler = new OpenRouterHandler(mockOptions) const mockStream = { @@ -242,15 +279,7 @@ describe("OpenRouterHandler", () => { test("completePrompt returns correct response", async () => { const handler = new OpenRouterHandler(mockOptions) - const mockResponse = { - choices: [ - { - message: { - content: "test completion", - }, - }, - ], - } + const mockResponse = { choices: [{ message: { content: "test completion" } }] } const mockCreate = jest.fn().mockResolvedValue(mockResponse) ;(OpenAI as jest.MockedClass).prototype.chat = { @@ -260,10 +289,13 @@ describe("OpenRouterHandler", () => { const result = await handler.completePrompt("test prompt") expect(result).toBe("test completion") + expect(mockCreate).toHaveBeenCalledWith({ model: mockOptions.openRouterModelId, - messages: [{ role: "user", content: "test prompt" }], + max_tokens: 1000, + thinking: undefined, temperature: 0, + messages: [{ role: "user", content: "test prompt" }], stream: false, }) }) @@ -292,8 +324,6 @@ describe("OpenRouterHandler", () => { completions: { create: mockCreate }, } as any - await expect(handler.completePrompt("test prompt")).rejects.toThrow( - "OpenRouter completion error: Unexpected error", - ) + await expect(handler.completePrompt("test prompt")).rejects.toThrow("Unexpected error") }) }) diff --git a/src/api/providers/__tests__/vertex.test.ts b/src/api/providers/__tests__/vertex.test.ts index 9cf92f0a16..7b74bd4cd7 100644 --- a/src/api/providers/__tests__/vertex.test.ts +++ b/src/api/providers/__tests__/vertex.test.ts @@ -6,6 +6,7 @@ import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta" import { VertexHandler } from "../vertex" import { ApiStreamChunk } from "../../transform/stream" +import { VertexAI } from "@google-cloud/vertexai" // Mock Vertex SDK jest.mock("@anthropic-ai/vertex-sdk", () => ({ @@ -49,24 +50,100 @@ jest.mock("@anthropic-ai/vertex-sdk", () => ({ })), })) +// Mock Vertex Gemini SDK +jest.mock("@google-cloud/vertexai", () => { + const mockGenerateContentStream = jest.fn().mockImplementation(() => { + return { + stream: { + async *[Symbol.asyncIterator]() { + yield { + candidates: [ + { + content: { + parts: [{ text: "Test Gemini response" }], + }, + }, + ], + } + }, + }, + response: { + usageMetadata: { + promptTokenCount: 5, + candidatesTokenCount: 10, + }, + }, + } + }) + + const mockGenerateContent = jest.fn().mockResolvedValue({ + response: { + candidates: [ + { + content: { + parts: [{ text: "Test Gemini response" }], + }, + }, + ], + }, + }) + + const mockGenerativeModel = jest.fn().mockImplementation(() => { + return { + generateContentStream: mockGenerateContentStream, + generateContent: mockGenerateContent, + } + }) + + return { + VertexAI: jest.fn().mockImplementation(() => { + return { + getGenerativeModel: mockGenerativeModel, + } + }), + GenerativeModel: mockGenerativeModel, + } +}) + describe("VertexHandler", () => { let handler: VertexHandler - beforeEach(() => { - handler = new VertexHandler({ - apiModelId: "claude-3-5-sonnet-v2@20241022", - vertexProjectId: "test-project", - vertexRegion: "us-central1", - }) - }) - describe("constructor", () => { - it("should initialize with provided config", () => { + it("should initialize with provided config for Claude", () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + expect(AnthropicVertex).toHaveBeenCalledWith({ projectId: "test-project", region: "us-central1", }) }) + + it("should initialize with provided config for Gemini", () => { + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + expect(VertexAI).toHaveBeenCalledWith({ + project: "test-project", + location: "us-central1", + }) + }) + + it("should throw error for invalid model", () => { + expect(() => { + new VertexHandler({ + apiModelId: "invalid-model", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + }).toThrow("Unknown model ID: invalid-model") + }) }) describe("createMessage", () => { @@ -83,7 +160,13 @@ describe("VertexHandler", () => { const systemPrompt = "You are a helpful assistant" - it("should handle streaming responses correctly", async () => { + it("should handle streaming responses correctly for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -127,7 +210,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -187,7 +270,58 @@ describe("VertexHandler", () => { }) }) - it("should handle multiple content blocks with line breaks", async () => { + it("should handle streaming responses correctly for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContentStream = mockGemini.VertexAI().getGenerativeModel().generateContentStream + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + const stream = handler.createMessage(systemPrompt, mockMessages) + const chunks: ApiStreamChunk[] = [] + + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks.length).toBe(2) + expect(chunks[0]).toEqual({ + type: "text", + text: "Test Gemini response", + }) + expect(chunks[1]).toEqual({ + type: "usage", + inputTokens: 5, + outputTokens: 10, + }) + + expect(mockGenerateContentStream).toHaveBeenCalledWith({ + contents: [ + { + role: "user", + parts: [{ text: "Hello" }], + }, + { + role: "model", + parts: [{ text: "Hi there!" }], + }, + ], + generationConfig: { + maxOutputTokens: 16384, + temperature: 0, + }, + }) + }) + + it("should handle multiple content blocks with line breaks for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "content_block_start", @@ -216,7 +350,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -240,10 +374,16 @@ describe("VertexHandler", () => { }) }) - it("should handle API errors", async () => { + it("should handle API errors for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockError = new Error("Vertex API error") const mockCreate = jest.fn().mockRejectedValue(mockError) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) @@ -254,7 +394,13 @@ describe("VertexHandler", () => { }).rejects.toThrow("Vertex API error") }) - it("should handle prompt caching for supported models", async () => { + it("should handle prompt caching for supported models for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -299,7 +445,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, [ { @@ -383,7 +529,13 @@ describe("VertexHandler", () => { ) }) - it("should handle cache-related usage metrics", async () => { + it("should handle cache-related usage metrics for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -415,7 +567,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -442,7 +594,13 @@ describe("VertexHandler", () => { const systemPrompt = "You are a helpful assistant" - it("should handle thinking content blocks and deltas", async () => { + it("should handle thinking content blocks and deltas for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -488,7 +646,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -510,7 +668,13 @@ describe("VertexHandler", () => { expect(textChunks[1].text).toBe("Here's my answer:") }) - it("should handle multiple thinking blocks with line breaks", async () => { + it("should handle multiple thinking blocks with line breaks for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "content_block_start", @@ -539,7 +703,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -565,10 +729,16 @@ describe("VertexHandler", () => { }) describe("completePrompt", () => { - it("should complete prompt successfully", async () => { + it("should complete prompt successfully for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const result = await handler.completePrompt("Test prompt") expect(result).toBe("Test response") - expect(handler["client"].messages.create).toHaveBeenCalledWith({ + expect(handler["anthropicClient"].messages.create).toHaveBeenCalledWith({ model: "claude-3-5-sonnet-v2@20241022", max_tokens: 8192, temperature: 0, @@ -583,31 +753,109 @@ describe("VertexHandler", () => { }) }) - it("should handle API errors", async () => { + it("should complete prompt successfully for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContent = mockGemini.VertexAI().getGenerativeModel().generateContent + + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("Test Gemini response") + expect(mockGenerateContent).toHaveBeenCalled() + expect(mockGenerateContent).toHaveBeenCalledWith({ + contents: [{ role: "user", parts: [{ text: "Test prompt" }] }], + generationConfig: { + temperature: 0, + }, + }) + }) + + it("should handle API errors for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockError = new Error("Vertex API error") const mockCreate = jest.fn().mockRejectedValue(mockError) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate await expect(handler.completePrompt("Test prompt")).rejects.toThrow( "Vertex completion error: Vertex API error", ) }) - it("should handle non-text content", async () => { + it("should handle API errors for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContent = mockGemini.VertexAI().getGenerativeModel().generateContent + mockGenerateContent.mockRejectedValue(new Error("Vertex API error")) + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + await expect(handler.completePrompt("Test prompt")).rejects.toThrow( + "Vertex completion error: Vertex API error", + ) + }) + + it("should handle non-text content for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockCreate = jest.fn().mockResolvedValue({ content: [{ type: "image" }], }) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const result = await handler.completePrompt("Test prompt") expect(result).toBe("") }) - it("should handle empty response", async () => { + it("should handle empty response for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockCreate = jest.fn().mockResolvedValue({ content: [{ type: "text", text: "" }], }) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate + + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + + it("should handle empty response for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContent = mockGemini.VertexAI().getGenerativeModel().generateContent + mockGenerateContent.mockResolvedValue({ + response: { + candidates: [ + { + content: { + parts: [{ text: "" }], + }, + }, + ], + }, + }) + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) const result = await handler.completePrompt("Test prompt") expect(result).toBe("") @@ -615,7 +863,13 @@ describe("VertexHandler", () => { }) describe("getModel", () => { - it("should return correct model info", () => { + it("should return correct model info for Claude", () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const modelInfo = handler.getModel() expect(modelInfo.id).toBe("claude-3-5-sonnet-v2@20241022") expect(modelInfo.info).toBeDefined() @@ -623,14 +877,46 @@ describe("VertexHandler", () => { expect(modelInfo.info.contextWindow).toBe(200_000) }) - it("should return default model if invalid model specified", () => { - const invalidHandler = new VertexHandler({ - apiModelId: "invalid-model", + it("should return correct model info for Gemini", () => { + handler = new VertexHandler({ + apiModelId: "gemini-2.0-flash-001", vertexProjectId: "test-project", vertexRegion: "us-central1", }) - const modelInfo = invalidHandler.getModel() - expect(modelInfo.id).toBe("claude-3-7-sonnet@20250219") // Default model + + const modelInfo = handler.getModel() + expect(modelInfo.id).toBe("gemini-2.0-flash-001") + expect(modelInfo.info).toBeDefined() + expect(modelInfo.info.maxTokens).toBe(8192) + expect(modelInfo.info.contextWindow).toBe(1048576) + }) + + it("honors custom maxTokens for thinking models", () => { + const handler = new VertexHandler({ + apiKey: "test-api-key", + apiModelId: "claude-3-7-sonnet@20250219:thinking", + modelMaxTokens: 32_768, + modelMaxThinkingTokens: 16_384, + }) + + const result = handler.getModel() + expect(result.maxTokens).toBe(32_768) + expect(result.thinking).toEqual({ type: "enabled", budget_tokens: 16_384 }) + expect(result.temperature).toBe(1.0) + }) + + it("does not honor custom maxTokens for non-thinking models", () => { + const handler = new VertexHandler({ + apiKey: "test-api-key", + apiModelId: "claude-3-7-sonnet@20250219", + modelMaxTokens: 32_768, + modelMaxThinkingTokens: 16_384, + }) + + const result = handler.getModel() + expect(result.maxTokens).toBe(16_384) + expect(result.thinking).toBeUndefined() + expect(result.temperature).toBe(0) }) }) @@ -724,7 +1010,7 @@ describe("VertexHandler", () => { }, } }) - ;(thinkingHandler["client"].messages as any).create = mockCreate + ;(thinkingHandler["anthropicClient"].messages as any).create = mockCreate await thinkingHandler .createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]) diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index fc0b99c59b..a23f99261e 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -1,7 +1,6 @@ import { Anthropic } from "@anthropic-ai/sdk" import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming" import { CacheControlEphemeral } from "@anthropic-ai/sdk/resources" -import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta" import { anthropicDefaultModelId, AnthropicModelId, @@ -9,18 +8,18 @@ import { ApiHandlerOptions, ModelInfo, } from "../../shared/api" -import { ApiHandler, SingleCompletionHandler } from "../index" import { ApiStream } from "../transform/stream" +import { BaseProvider } from "./base-provider" +import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "./constants" +import { SingleCompletionHandler, getModelParams } from "../index" -const ANTHROPIC_DEFAULT_TEMPERATURE = 0 - -export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { +export class AnthropicHandler extends BaseProvider implements SingleCompletionHandler { private options: ApiHandlerOptions private client: Anthropic constructor(options: ApiHandlerOptions) { + super() this.options = options - this.client = new Anthropic({ apiKey: this.options.apiKey, baseURL: this.options.anthropicBaseUrl || undefined, @@ -30,7 +29,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { let stream: AnthropicStream const cacheControl: CacheControlEphemeral = { type: "ephemeral" } - let { id: modelId, temperature, maxTokens, thinking } = this.getModel() + let { id: modelId, maxTokens, thinking, temperature } = this.getModel() switch (modelId) { case "claude-3-7-sonnet-20250219": @@ -53,7 +52,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { stream = await this.client.messages.create( { model: modelId, - max_tokens: maxTokens, + max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS, temperature, thinking, // Setting cache breakpoint for system prompt so new tasks can reuse it. @@ -101,7 +100,7 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { default: { stream = (await this.client.messages.create({ model: modelId, - max_tokens: maxTokens, + max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS, temperature, system: [{ text: systemPrompt, type: "text" }], messages, @@ -182,55 +181,31 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { getModel() { const modelId = this.options.apiModelId - let temperature = this.options.modelTemperature ?? ANTHROPIC_DEFAULT_TEMPERATURE - let thinking: BetaThinkingConfigParam | undefined = undefined + let id = modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId + const info: ModelInfo = anthropicModels[id] - if (modelId && modelId in anthropicModels) { - let id = modelId as AnthropicModelId - const info: ModelInfo = anthropicModels[id] - - // The `:thinking` variant is a virtual identifier for the - // `claude-3-7-sonnet-20250219` model with a thinking budget. - // We can handle this more elegantly in the future. - if (id === "claude-3-7-sonnet-20250219:thinking") { - id = "claude-3-7-sonnet-20250219" - } - - const maxTokens = this.options.modelMaxTokens || info.maxTokens || 8192 - - if (info.thinking) { - // Anthropic "Thinking" models require a temperature of 1.0. - temperature = 1.0 - - // Clamp the thinking budget to be at most 80% of max tokens and at - // least 1024 tokens. - const maxBudgetTokens = Math.floor(maxTokens * 0.8) - const budgetTokens = Math.max( - Math.min(this.options.modelMaxThinkingTokens ?? maxBudgetTokens, maxBudgetTokens), - 1024, - ) - - thinking = { type: "enabled", budget_tokens: budgetTokens } - } - - return { id, info, temperature, maxTokens, thinking } + // The `:thinking` variant is a virtual identifier for the + // `claude-3-7-sonnet-20250219` model with a thinking budget. + // We can handle this more elegantly in the future. + if (id === "claude-3-7-sonnet-20250219:thinking") { + id = "claude-3-7-sonnet-20250219" } - const id = anthropicDefaultModelId - const info: ModelInfo = anthropicModels[id] - const maxTokens = this.options.modelMaxTokens || info.maxTokens || 8192 - - return { id, info, temperature, maxTokens, thinking } + return { + id, + info, + ...getModelParams({ options: this.options, model: info, defaultMaxTokens: ANTHROPIC_DEFAULT_MAX_TOKENS }), + } } async completePrompt(prompt: string) { - let { id: modelId, temperature, maxTokens, thinking } = this.getModel() + let { id: modelId, maxTokens, thinking, temperature } = this.getModel() const message = await this.client.messages.create({ model: modelId, - max_tokens: maxTokens, - temperature, + max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS, thinking, + temperature, messages: [{ role: "user", content: prompt }], stream: false, }) @@ -238,4 +213,35 @@ export class AnthropicHandler implements ApiHandler, SingleCompletionHandler { const content = message.content.find(({ type }) => type === "text") return content?.type === "text" ? content.text : "" } + + /** + * Counts tokens for the given content using Anthropic's API + * + * @param content The content blocks to count tokens for + * @returns A promise resolving to the token count + */ + override async countTokens(content: Array): Promise { + try { + // Use the current model + const actualModelId = this.getModel().id + + const response = await this.client.messages.countTokens({ + model: actualModelId, + messages: [ + { + role: "user", + content: content, + }, + ], + }) + + return response.input_tokens + } catch (error) { + // Log error but fallback to tiktoken estimation + console.warn("Anthropic token counting failed, using fallback", error) + + // Use the base provider's implementation as fallback + return super.countTokens(content) + } + } } diff --git a/src/api/providers/base-provider.ts b/src/api/providers/base-provider.ts new file mode 100644 index 0000000000..34156e4adf --- /dev/null +++ b/src/api/providers/base-provider.ts @@ -0,0 +1,64 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import { ApiHandler } from ".." +import { ModelInfo } from "../../shared/api" +import { ApiStream } from "../transform/stream" +import { Tiktoken } from "js-tiktoken/lite" +import o200kBase from "js-tiktoken/ranks/o200k_base" + +// Reuse the fudge factor used in the original code +const TOKEN_FUDGE_FACTOR = 1.5 + +/** + * Base class for API providers that implements common functionality + */ +export abstract class BaseProvider implements ApiHandler { + // Cache the Tiktoken encoder instance since it's stateless + private encoder: Tiktoken | null = null + abstract createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream + abstract getModel(): { id: string; info: ModelInfo } + + /** + * Default token counting implementation using tiktoken + * Providers can override this to use their native token counting endpoints + * + * Uses a cached Tiktoken encoder instance for performance since it's stateless. + * The encoder is created lazily on first use and reused for subsequent calls. + * + * @param content The content to count tokens for + * @returns A promise resolving to the token count + */ + async countTokens(content: Array): Promise { + if (!content || content.length === 0) return 0 + + let totalTokens = 0 + + // Lazily create and cache the encoder if it doesn't exist + if (!this.encoder) { + this.encoder = new Tiktoken(o200kBase) + } + + // Process each content block using the cached encoder + for (const block of content) { + if (block.type === "text") { + // Use tiktoken for text token counting + const text = block.text || "" + if (text.length > 0) { + const tokens = this.encoder.encode(text) + totalTokens += tokens.length + } + } else if (block.type === "image") { + // For images, calculate based on data size + const imageSource = block.source + if (imageSource && typeof imageSource === "object" && "data" in imageSource) { + const base64Data = imageSource.data as string + totalTokens += Math.ceil(Math.sqrt(base64Data.length)) + } else { + totalTokens += 300 // Conservative estimate for unknown images + } + } + } + + // Add a fudge factor to account for the fact that tiktoken is not always accurate + return Math.ceil(totalTokens * TOKEN_FUDGE_FACTOR) + } +} diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 3bca70338d..2deb019dc3 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -6,10 +6,11 @@ import { } from "@aws-sdk/client-bedrock-runtime" import { fromIni } from "@aws-sdk/credential-providers" import { Anthropic } from "@anthropic-ai/sdk" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" import { ApiHandlerOptions, BedrockModelId, ModelInfo, bedrockDefaultModelId, bedrockModels } from "../../shared/api" import { ApiStream } from "../transform/stream" import { convertToBedrockConverseMessages } from "../transform/bedrock-converse-format" +import { BaseProvider } from "./base-provider" const BEDROCK_DEFAULT_TEMPERATURE = 0.3 @@ -46,11 +47,12 @@ export interface StreamEvent { } } -export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class AwsBedrockHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: BedrockRuntimeClient constructor(options: ApiHandlerOptions) { + super() this.options = options const clientConfig: BedrockRuntimeClientConfig = { @@ -74,7 +76,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler { this.client = new BedrockRuntimeClient(clientConfig) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const modelConfig = this.getModel() // Handle cross-region inference @@ -205,7 +207,7 @@ export class AwsBedrockHandler implements ApiHandler, SingleCompletionHandler { } } - getModel(): { id: BedrockModelId | string; info: ModelInfo } { + override getModel(): { id: BedrockModelId | string; info: ModelInfo } { const modelId = this.options.apiModelId if (modelId) { // For tests, allow any model ID diff --git a/src/api/providers/constants.ts b/src/api/providers/constants.ts new file mode 100644 index 0000000000..86ca71746e --- /dev/null +++ b/src/api/providers/constants.ts @@ -0,0 +1,3 @@ +export const ANTHROPIC_DEFAULT_MAX_TOKENS = 8192 + +export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts index 0d7179320c..4e522b3fcb 100644 --- a/src/api/providers/gemini.ts +++ b/src/api/providers/gemini.ts @@ -1,22 +1,24 @@ import { Anthropic } from "@anthropic-ai/sdk" import { GoogleGenerativeAI } from "@google/generative-ai" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "../../shared/api" import { convertAnthropicMessageToGemini } from "../transform/gemini-format" import { ApiStream } from "../transform/stream" +import { BaseProvider } from "./base-provider" const GEMINI_DEFAULT_TEMPERATURE = 0 -export class GeminiHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class GeminiHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: GoogleGenerativeAI constructor(options: ApiHandlerOptions) { + super() this.options = options this.client = new GoogleGenerativeAI(options.geminiApiKey ?? "not-provided") } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const model = this.client.getGenerativeModel({ model: this.getModel().id, systemInstruction: systemPrompt, @@ -44,7 +46,7 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler { } } - getModel(): { id: GeminiModelId; info: ModelInfo } { + override getModel(): { id: GeminiModelId; info: ModelInfo } { const modelId = this.options.apiModelId if (modelId && modelId in geminiModels) { const id = modelId as GeminiModelId diff --git a/src/api/providers/glama.ts b/src/api/providers/glama.ts index 946d28d24f..6de435c4a2 100644 --- a/src/api/providers/glama.ts +++ b/src/api/providers/glama.ts @@ -6,22 +6,39 @@ import { ApiHandlerOptions, ModelInfo, glamaDefaultModelId, glamaDefaultModelInf import { parseApiPrice } from "../../utils/cost" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" +import { BaseProvider } from "./base-provider" const GLAMA_DEFAULT_TEMPERATURE = 0 -export class GlamaHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class GlamaHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: OpenAI constructor(options: ApiHandlerOptions) { + super() this.options = options const baseURL = "https://glama.ai/api/gateway/openai/v1" const apiKey = this.options.glamaApiKey ?? "not-provided" this.client = new OpenAI({ baseURL, apiKey }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + private supportsTemperature(): boolean { + return !this.getModel().id.startsWith("openai/o3-mini") + } + + override getModel(): { id: string; info: ModelInfo } { + const modelId = this.options.glamaModelId + const modelInfo = this.options.glamaModelInfo + + if (modelId && modelInfo) { + return { id: modelId, info: modelInfo } + } + + return { id: glamaDefaultModelId, info: glamaDefaultModelInfo } + } + + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { // Convert Anthropic messages to OpenAI format const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ { role: "system", content: systemPrompt }, @@ -152,21 +169,6 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler { } } - private supportsTemperature(): boolean { - return !this.getModel().id.startsWith("openai/o3-mini") - } - - getModel(): { id: string; info: ModelInfo } { - const modelId = this.options.glamaModelId - const modelInfo = this.options.glamaModelInfo - - if (modelId && modelInfo) { - return { id: modelId, info: modelInfo } - } - - return { id: glamaDefaultModelId, info: glamaDefaultModelInfo } - } - async completePrompt(prompt: string): Promise { try { const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { diff --git a/src/api/providers/lmstudio.ts b/src/api/providers/lmstudio.ts index beb3bd1b79..5308ebb85c 100644 --- a/src/api/providers/lmstudio.ts +++ b/src/api/providers/lmstudio.ts @@ -2,18 +2,20 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" import axios from "axios" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" +import { BaseProvider } from "./base-provider" const LMSTUDIO_DEFAULT_TEMPERATURE = 0 -export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class LmStudioHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: OpenAI constructor(options: ApiHandlerOptions) { + super() this.options = options this.client = new OpenAI({ baseURL: (this.options.lmStudioBaseUrl || "http://localhost:1234") + "/v1", @@ -21,7 +23,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ { role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages), @@ -51,7 +53,7 @@ export class LmStudioHandler implements ApiHandler, SingleCompletionHandler { } } - getModel(): { id: string; info: ModelInfo } { + override getModel(): { id: string; info: ModelInfo } { return { id: this.options.lmStudioModelId || "", info: openAiModelInfoSaneDefaults, diff --git a/src/api/providers/mistral.ts b/src/api/providers/mistral.ts index 08054c36b6..38f753c261 100644 --- a/src/api/providers/mistral.ts +++ b/src/api/providers/mistral.ts @@ -1,6 +1,6 @@ import { Anthropic } from "@anthropic-ai/sdk" import { Mistral } from "@mistralai/mistralai" -import { ApiHandler } from "../" +import { SingleCompletionHandler } from "../" import { ApiHandlerOptions, mistralDefaultModelId, @@ -13,14 +13,16 @@ import { } from "../../shared/api" import { convertToMistralMessages } from "../transform/mistral-format" import { ApiStream } from "../transform/stream" +import { BaseProvider } from "./base-provider" const MISTRAL_DEFAULT_TEMPERATURE = 0 -export class MistralHandler implements ApiHandler { - private options: ApiHandlerOptions +export class MistralHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: Mistral constructor(options: ApiHandlerOptions) { + super() if (!options.mistralApiKey) { throw new Error("Mistral API key is required") } @@ -48,7 +50,7 @@ export class MistralHandler implements ApiHandler { return "https://api.mistral.ai" } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const response = await this.client.chat.stream({ model: this.options.apiModelId || mistralDefaultModelId, messages: [{ role: "system", content: systemPrompt }, ...convertToMistralMessages(messages)], @@ -81,7 +83,7 @@ export class MistralHandler implements ApiHandler { } } - getModel(): { id: MistralModelId; info: ModelInfo } { + override getModel(): { id: MistralModelId; info: ModelInfo } { const modelId = this.options.apiModelId if (modelId && modelId in mistralModels) { const id = modelId as MistralModelId diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts index de7df5d261..26374d5d58 100644 --- a/src/api/providers/ollama.ts +++ b/src/api/providers/ollama.ts @@ -2,21 +2,21 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" import axios from "axios" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" import { ApiStream } from "../transform/stream" -import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./openai" +import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./constants" import { XmlMatcher } from "../../utils/xml-matcher" +import { BaseProvider } from "./base-provider" -const OLLAMA_DEFAULT_TEMPERATURE = 0 - -export class OllamaHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class OllamaHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: OpenAI constructor(options: ApiHandlerOptions) { + super() this.options = options this.client = new OpenAI({ baseURL: (this.options.ollamaBaseUrl || "http://localhost:11434") + "/v1", @@ -24,7 +24,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const modelId = this.getModel().id const useR1Format = modelId.toLowerCase().includes("deepseek-r1") const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ @@ -35,7 +35,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { const stream = await this.client.chat.completions.create({ model: this.getModel().id, messages: openAiMessages, - temperature: this.options.modelTemperature ?? OLLAMA_DEFAULT_TEMPERATURE, + temperature: this.options.modelTemperature ?? 0, stream: true, }) const matcher = new XmlMatcher( @@ -60,7 +60,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { } } - getModel(): { id: string; info: ModelInfo } { + override getModel(): { id: string; info: ModelInfo } { return { id: this.options.ollamaModelId || "", info: openAiModelInfoSaneDefaults, @@ -76,9 +76,7 @@ export class OllamaHandler implements ApiHandler, SingleCompletionHandler { messages: useR1Format ? convertToR1Format([{ role: "user", content: prompt }]) : [{ role: "user", content: prompt }], - temperature: - this.options.modelTemperature ?? - (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : OLLAMA_DEFAULT_TEMPERATURE), + temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), stream: false, }) return response.choices[0]?.message.content || "" diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 8feeafdb96..1fe7ef2a86 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -1,6 +1,6 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" import { ApiHandlerOptions, ModelInfo, @@ -10,20 +10,22 @@ import { } from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" +import { BaseProvider } from "./base-provider" const OPENAI_NATIVE_DEFAULT_TEMPERATURE = 0 -export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class OpenAiNativeHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: OpenAI constructor(options: ApiHandlerOptions) { + super() this.options = options const apiKey = this.options.openAiNativeApiKey ?? "not-provided" this.client = new OpenAI({ apiKey }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const modelId = this.getModel().id if (modelId.startsWith("o1")) { @@ -133,7 +135,7 @@ export class OpenAiNativeHandler implements ApiHandler, SingleCompletionHandler } } - getModel(): { id: OpenAiNativeModelId; info: ModelInfo } { + override getModel(): { id: OpenAiNativeModelId; info: ModelInfo } { const modelId = this.options.apiModelId if (modelId && modelId in openAiNativeModels) { const id = modelId as OpenAiNativeModelId diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index f1c404d50a..0fa833e82a 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -8,24 +8,24 @@ import { ModelInfo, openAiModelInfoSaneDefaults, } from "../../shared/api" -import { ApiHandler, SingleCompletionHandler } from "../index" +import { SingleCompletionHandler } from "../index" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" import { convertToSimpleMessages } from "../transform/simple-format" import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" +import { BaseProvider } from "./base-provider" +const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 export interface OpenAiHandlerOptions extends ApiHandlerOptions { defaultHeaders?: Record } -export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 -const OPENAI_DEFAULT_TEMPERATURE = 0 - -export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { +export class OpenAiHandler extends BaseProvider implements SingleCompletionHandler { protected options: OpenAiHandlerOptions private client: OpenAI constructor(options: OpenAiHandlerOptions) { + super() this.options = options const baseURL = this.options.openAiBaseUrl ?? "https://api.openai.com/v1" @@ -53,7 +53,7 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { } } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const modelInfo = this.getModel().info const modelUrl = this.options.openAiBaseUrl ?? "" const modelId = this.options.openAiModelId ?? "" @@ -78,9 +78,7 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, - temperature: - this.options.modelTemperature ?? - (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : OPENAI_DEFAULT_TEMPERATURE), + temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), messages: convertedMessages, stream: true as const, stream_options: { include_usage: true }, @@ -143,7 +141,7 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler { } } - getModel(): { id: string; info: ModelInfo } { + override getModel(): { id: string; info: ModelInfo } { return { id: this.options.openAiModelId ?? "", info: this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults, diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 82c02e20a7..0bf98ba18e 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -9,10 +9,10 @@ import { parseApiPrice } from "../../utils/cost" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStreamChunk, ApiStreamUsageChunk } from "../transform/stream" import { convertToR1Format } from "../transform/r1-format" -import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./openai" -import { ApiHandler, SingleCompletionHandler } from ".." -const OPENROUTER_DEFAULT_TEMPERATURE = 0 +import { DEEP_SEEK_DEFAULT_TEMPERATURE } from "./constants" +import { getModelParams, SingleCompletionHandler } from ".." +import { BaseProvider } from "./base-provider" // Add custom interface for OpenRouter params. type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { @@ -26,11 +26,12 @@ interface OpenRouterApiStreamUsageChunk extends ApiStreamUsageChunk { fullResponseText: string } -export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class OpenRouterHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: OpenAI constructor(options: ApiHandlerOptions) { + super() this.options = options const baseURL = this.options.openRouterBaseUrl || "https://openrouter.ai/api/v1" @@ -44,17 +45,22 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { this.client = new OpenAI({ baseURL, apiKey, defaultHeaders }) } - async *createMessage( + override async *createMessage( systemPrompt: string, messages: Anthropic.Messages.MessageParam[], ): AsyncGenerator { - // Convert Anthropic messages to OpenAI format + let { id: modelId, maxTokens, thinking, temperature, topP } = this.getModel() + + // Convert Anthropic messages to OpenAI format. let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ { role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages), ] - const { id: modelId, info: modelInfo } = this.getModel() + // DeepSeek highly recommends using user instead of system role. + if (modelId.startsWith("deepseek/deepseek-r1") || modelId === "perplexity/sonar-reasoning") { + openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) + } // prompt caching: https://openrouter.ai/docs/prompt-caching // this is specifically for claude models (some models may 'support prompt caching' automatically without this) @@ -95,42 +101,12 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { break } - let defaultTemperature = OPENROUTER_DEFAULT_TEMPERATURE - let topP: number | undefined = undefined - - // Handle models based on deepseek-r1 - if (modelId.startsWith("deepseek/deepseek-r1") || modelId === "perplexity/sonar-reasoning") { - // Recommended temperature for DeepSeek reasoning models - defaultTemperature = DEEP_SEEK_DEFAULT_TEMPERATURE - // DeepSeek highly recommends using user instead of system role - openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) - // Some provider support topP and 0.95 is value that Deepseek used in their benchmarks - topP = 0.95 - } - - const maxTokens = this.options.modelMaxTokens || modelInfo.maxTokens - let temperature = this.options.modelTemperature ?? defaultTemperature - let thinking: BetaThinkingConfigParam | undefined = undefined - - if (modelInfo.thinking) { - // Clamp the thinking budget to be at most 80% of max tokens and at - // least 1024 tokens. - const maxBudgetTokens = Math.floor((maxTokens || 8192) * 0.8) - const budgetTokens = Math.max( - Math.min(this.options.modelMaxThinkingTokens ?? maxBudgetTokens, maxBudgetTokens), - 1024, - ) - - thinking = { type: "enabled", budget_tokens: budgetTokens } - temperature = 1.0 - } - // https://openrouter.ai/docs/transforms let fullResponseText = "" const completionParams: OpenRouterChatCompletionParams = { model: modelId, - max_tokens: modelInfo.maxTokens, + max_tokens: maxTokens, temperature, thinking, // OpenRouter is temporarily supporting this. top_p: topP, @@ -138,7 +114,7 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { stream: true, include_reasoning: true, // This way, the transforms field will only be included in the parameters when openRouterUseMiddleOutTransform is true. - ...(this.options.openRouterUseMiddleOutTransform && { transforms: ["middle-out"] }), + ...((this.options.openRouterUseMiddleOutTransform ?? true) && { transforms: ["middle-out"] }), } const stream = await this.client.chat.completions.create(completionParams) @@ -218,37 +194,46 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler { } } - getModel() { + override getModel() { const modelId = this.options.openRouterModelId const modelInfo = this.options.openRouterModelInfo - return modelId && modelInfo - ? { id: modelId, info: modelInfo } - : { id: openRouterDefaultModelId, info: openRouterDefaultModelInfo } + + let id = modelId ?? openRouterDefaultModelId + const info = modelInfo ?? openRouterDefaultModelInfo + + const isDeepSeekR1 = id.startsWith("deepseek/deepseek-r1") || modelId === "perplexity/sonar-reasoning" + const defaultTemperature = isDeepSeekR1 ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0 + const topP = isDeepSeekR1 ? 0.95 : undefined + + return { + id, + info, + ...getModelParams({ options: this.options, model: info, defaultTemperature }), + topP, + } } - async completePrompt(prompt: string): Promise { - try { - const response = await this.client.chat.completions.create({ - model: this.getModel().id, - messages: [{ role: "user", content: prompt }], - temperature: this.options.modelTemperature ?? OPENROUTER_DEFAULT_TEMPERATURE, - stream: false, - }) + async completePrompt(prompt: string) { + let { id: modelId, maxTokens, thinking, temperature } = this.getModel() - if ("error" in response) { - const error = response.error as { message?: string; code?: number } - throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`) - } - - const completion = response as OpenAI.Chat.ChatCompletion - return completion.choices[0]?.message?.content || "" - } catch (error) { - if (error instanceof Error) { - throw new Error(`OpenRouter completion error: ${error.message}`) - } - - throw error + const completionParams: OpenRouterChatCompletionParams = { + model: modelId, + max_tokens: maxTokens, + thinking, + temperature, + messages: [{ role: "user", content: prompt }], + stream: false, } + + const response = await this.client.chat.completions.create(completionParams) + + if ("error" in response) { + const error = response.error as { message?: string; code?: number } + throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`) + } + + const completion = response as OpenAI.Chat.ChatCompletion + return completion.choices[0]?.message?.content || "" } } @@ -278,7 +263,7 @@ export async function getOpenRouterModels() { modelInfo.supportsPromptCache = true modelInfo.cacheWritesPrice = 3.75 modelInfo.cacheReadsPrice = 0.3 - modelInfo.maxTokens = 64_000 + modelInfo.maxTokens = rawModel.id === "anthropic/claude-3.7-sonnet:thinking" ? 64_000 : 16_384 break case rawModel.id.startsWith("anthropic/claude-3.5-sonnet-20240620"): modelInfo.supportsPromptCache = true diff --git a/src/api/providers/unbound.ts b/src/api/providers/unbound.ts index 5e3ad8843b..e3894f8718 100644 --- a/src/api/providers/unbound.ts +++ b/src/api/providers/unbound.ts @@ -5,25 +5,27 @@ import OpenAI from "openai" import { ApiHandlerOptions, ModelInfo, unboundDefaultModelId, unboundDefaultModelInfo } from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" +import { BaseProvider } from "./base-provider" interface UnboundUsage extends OpenAI.CompletionUsage { cache_creation_input_tokens?: number cache_read_input_tokens?: number } -export class UnboundHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class UnboundHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: OpenAI constructor(options: ApiHandlerOptions) { + super() this.options = options const baseURL = "https://api.getunbound.ai/v1" const apiKey = this.options.unboundApiKey ?? "not-provided" this.client = new OpenAI({ baseURL, apiKey }) } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { // Convert Anthropic messages to OpenAI format const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ { role: "system", content: systemPrompt }, @@ -131,7 +133,7 @@ export class UnboundHandler implements ApiHandler, SingleCompletionHandler { } } - getModel(): { id: string; info: ModelInfo } { + override getModel(): { id: string; info: ModelInfo } { const modelId = this.options.unboundModelId const modelInfo = this.options.unboundModelInfo if (modelId && modelInfo) { diff --git a/src/api/providers/vertex.ts b/src/api/providers/vertex.ts index a25fad07ee..7f764a9076 100644 --- a/src/api/providers/vertex.ts +++ b/src/api/providers/vertex.ts @@ -1,10 +1,16 @@ import { Anthropic } from "@anthropic-ai/sdk" import { AnthropicVertex } from "@anthropic-ai/vertex-sdk" import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming" -import { ApiHandler, SingleCompletionHandler } from "../" -import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta" + +import { VertexAI } from "@google-cloud/vertexai" + import { ApiHandlerOptions, ModelInfo, vertexDefaultModelId, VertexModelId, vertexModels } from "../../shared/api" import { ApiStream } from "../transform/stream" +import { convertAnthropicMessageToVertexGemini } from "../transform/vertex-gemini-format" +import { BaseProvider } from "./base-provider" + +import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "./constants" +import { getModelParams, SingleCompletionHandler } from "../" // Types for Vertex SDK @@ -93,17 +99,37 @@ interface VertexMessageStreamEvent { } // https://docs.anthropic.com/en/api/claude-on-vertex-ai -export class VertexHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions - private client: AnthropicVertex +export class VertexHandler extends BaseProvider implements SingleCompletionHandler { + MODEL_CLAUDE = "claude" + MODEL_GEMINI = "gemini" + + protected options: ApiHandlerOptions + private anthropicClient: AnthropicVertex + private geminiClient: VertexAI + private modelType: string constructor(options: ApiHandlerOptions) { + super() this.options = options - this.client = new AnthropicVertex({ + + if (this.options.apiModelId?.startsWith(this.MODEL_CLAUDE)) { + this.modelType = this.MODEL_CLAUDE + } else if (this.options.apiModelId?.startsWith(this.MODEL_GEMINI)) { + this.modelType = this.MODEL_GEMINI + } else { + throw new Error(`Unknown model ID: ${this.options.apiModelId}`) + } + + this.anthropicClient = new AnthropicVertex({ projectId: this.options.vertexProjectId ?? "not-provided", // https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#regions region: this.options.vertexRegion ?? "us-east5", }) + + this.geminiClient = new VertexAI({ + project: this.options.vertexProjectId ?? "not-provided", + location: this.options.vertexRegion ?? "us-east5", + }) } private formatMessageForCache(message: Anthropic.Messages.MessageParam, shouldCache: boolean): VertexMessage { @@ -154,7 +180,43 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { } } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + private async *createGeminiMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const model = this.geminiClient.getGenerativeModel({ + model: this.getModel().id, + systemInstruction: systemPrompt, + }) + + const result = await model.generateContentStream({ + contents: messages.map(convertAnthropicMessageToVertexGemini), + generationConfig: { + maxOutputTokens: this.getModel().info.maxTokens, + temperature: this.options.modelTemperature ?? 0, + }, + }) + + for await (const chunk of result.stream) { + if (chunk.candidates?.[0]?.content?.parts) { + for (const part of chunk.candidates[0].content.parts) { + if (part.text) { + yield { + type: "text", + text: part.text, + } + } + } + } + } + + const response = await result.response + + yield { + type: "usage", + inputTokens: response.usageMetadata?.promptTokenCount ?? 0, + outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0, + } + } + + private async *createClaudeMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const model = this.getModel() let { id, info, temperature, maxTokens, thinking } = model const useCache = model.info.supportsPromptCache @@ -192,7 +254,7 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { stream: true, } - const stream = (await this.client.messages.create( + const stream = (await this.anthropicClient.messages.create( params as Anthropic.Messages.MessageCreateParamsStreaming, )) as unknown as AnthropicStream @@ -272,58 +334,77 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { } } - getModel(): { - id: VertexModelId - info: ModelInfo - temperature: number - maxTokens: number - thinking?: BetaThinkingConfigParam - } { - const modelId = this.options.apiModelId - let temperature = this.options.modelTemperature ?? 0 - let thinking: BetaThinkingConfigParam | undefined = undefined - - if (modelId && modelId in vertexModels) { - const id = modelId as VertexModelId - const info: ModelInfo = vertexModels[id] - - // The `:thinking` variant is a virtual identifier for thinking-enabled models - // Similar to how it's handled in the Anthropic provider - let actualId = id - if (id.endsWith(":thinking")) { - actualId = id.replace(":thinking", "") as VertexModelId + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + switch (this.modelType) { + case this.MODEL_CLAUDE: { + yield* this.createClaudeMessage(systemPrompt, messages) + break } - - const maxTokens = this.options.modelMaxTokens || info.maxTokens || 8192 - - if (info.thinking) { - temperature = 1.0 // Thinking requires temperature 1.0 - const maxBudgetTokens = Math.floor(maxTokens * 0.8) - const budgetTokens = Math.max( - Math.min(this.options.modelMaxThinkingTokens ?? maxBudgetTokens, maxBudgetTokens), - 1024, - ) - thinking = { type: "enabled", budget_tokens: budgetTokens } + case this.MODEL_GEMINI: { + yield* this.createGeminiMessage(systemPrompt, messages) + break + } + default: { + throw new Error(`Invalid model type: ${this.modelType}`) } - - return { id: actualId, info, temperature, maxTokens, thinking } } - - const id = vertexDefaultModelId - const info = vertexModels[id] - const maxTokens = this.options.modelMaxTokens || info.maxTokens || 8192 - - return { id, info, temperature, maxTokens, thinking } } - async completePrompt(prompt: string): Promise { + getModel() { + const modelId = this.options.apiModelId + let id = modelId && modelId in vertexModels ? (modelId as VertexModelId) : vertexDefaultModelId + const info: ModelInfo = vertexModels[id] + + // The `:thinking` variant is a virtual identifier for thinking-enabled + // models (similar to how it's handled in the Anthropic provider.) + if (id.endsWith(":thinking")) { + id = id.replace(":thinking", "") as VertexModelId + } + + return { + id, + info, + ...getModelParams({ options: this.options, model: info, defaultMaxTokens: ANTHROPIC_DEFAULT_MAX_TOKENS }), + } + } + + private async completePromptGemini(prompt: string) { + try { + const model = this.geminiClient.getGenerativeModel({ + model: this.getModel().id, + }) + + const result = await model.generateContent({ + contents: [{ role: "user", parts: [{ text: prompt }] }], + generationConfig: { + temperature: this.options.modelTemperature ?? 0, + }, + }) + + let text = "" + result.response.candidates?.forEach((candidate) => { + candidate.content.parts.forEach((part) => { + text += part.text + }) + }) + + return text + } catch (error) { + if (error instanceof Error) { + throw new Error(`Vertex completion error: ${error.message}`) + } + throw error + } + } + + private async completePromptClaude(prompt: string) { try { let { id, info, temperature, maxTokens, thinking } = this.getModel() const useCache = info.supportsPromptCache - const params = { + const params: Anthropic.Messages.MessageCreateParamsNonStreaming = { model: id, - max_tokens: maxTokens, + max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS, temperature, thinking, system: "", // No system prompt needed for single completions @@ -344,20 +425,34 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { stream: false, } - const response = (await this.client.messages.create( - params as Anthropic.Messages.MessageCreateParamsNonStreaming, - )) as unknown as VertexMessageResponse - + const response = (await this.anthropicClient.messages.create(params)) as unknown as VertexMessageResponse const content = response.content[0] + if (content.type === "text") { return content.text } + return "" } catch (error) { if (error instanceof Error) { throw new Error(`Vertex completion error: ${error.message}`) } + throw error } } + + async completePrompt(prompt: string) { + switch (this.modelType) { + case this.MODEL_CLAUDE: { + return this.completePromptClaude(prompt) + } + case this.MODEL_GEMINI: { + return this.completePromptGemini(prompt) + } + default: { + throw new Error(`Invalid model type: ${this.modelType}`) + } + } + } } diff --git a/src/api/providers/vscode-lm.ts b/src/api/providers/vscode-lm.ts index 28e24231a2..bf1215e238 100644 --- a/src/api/providers/vscode-lm.ts +++ b/src/api/providers/vscode-lm.ts @@ -1,18 +1,19 @@ import { Anthropic } from "@anthropic-ai/sdk" import * as vscode from "vscode" -import { ApiHandler, SingleCompletionHandler } from "../" +import { SingleCompletionHandler } from "../" import { calculateApiCost } from "../../utils/cost" import { ApiStream } from "../transform/stream" import { convertToVsCodeLmMessages } from "../transform/vscode-lm-format" import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils" import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api" +import { BaseProvider } from "./base-provider" /** * Handles interaction with VS Code's Language Model API for chat-based operations. - * This handler implements the ApiHandler interface to provide VS Code LM specific functionality. + * This handler extends BaseProvider to provide VS Code LM specific functionality. * - * @implements {ApiHandler} + * @extends {BaseProvider} * * @remarks * The handler manages a VS Code language model chat client and provides methods to: @@ -35,13 +36,14 @@ import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../.. * } * ``` */ -export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler { - private options: ApiHandlerOptions +export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions private client: vscode.LanguageModelChat | null private disposable: vscode.Disposable | null private currentRequestCancellation: vscode.CancellationTokenSource | null constructor(options: ApiHandlerOptions) { + super() this.options = options this.client = null this.disposable = null @@ -145,7 +147,33 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler { } } - private async countTokens(text: string | vscode.LanguageModelChatMessage): Promise { + /** + * Implements the ApiHandler countTokens interface method + * Provides token counting for Anthropic content blocks + * + * @param content The content blocks to count tokens for + * @returns A promise resolving to the token count + */ + override async countTokens(content: Array): Promise { + // Convert Anthropic content blocks to a string for VSCode LM token counting + let textContent = "" + + for (const block of content) { + if (block.type === "text") { + textContent += block.text || "" + } else if (block.type === "image") { + // VSCode LM doesn't support images directly, so we'll just use a placeholder + textContent += "[IMAGE]" + } + } + + return this.internalCountTokens(textContent) + } + + /** + * Private implementation of token counting used internally by VsCodeLmHandler + */ + private async internalCountTokens(text: string | vscode.LanguageModelChatMessage): Promise { // Check for required dependencies if (!this.client) { console.warn("Roo Code : No client available for token counting") @@ -216,9 +244,9 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler { systemPrompt: string, vsCodeLmMessages: vscode.LanguageModelChatMessage[], ): Promise { - const systemTokens: number = await this.countTokens(systemPrompt) + const systemTokens: number = await this.internalCountTokens(systemPrompt) - const messageTokens: number[] = await Promise.all(vsCodeLmMessages.map((msg) => this.countTokens(msg))) + const messageTokens: number[] = await Promise.all(vsCodeLmMessages.map((msg) => this.internalCountTokens(msg))) return systemTokens + messageTokens.reduce((sum: number, tokens: number): number => sum + tokens, 0) } @@ -319,7 +347,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler { return content } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { // Ensure clean state before starting a new request this.ensureCleanState() const client: vscode.LanguageModelChat = await this.getClient() @@ -427,7 +455,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler { } // Count tokens in the accumulated text after stream completion - const totalOutputTokens: number = await this.countTokens(accumulatedText) + const totalOutputTokens: number = await this.internalCountTokens(accumulatedText) // Report final usage after stream completion yield { @@ -467,7 +495,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler { } // Return model information based on the current client state - getModel(): { id: string; info: ModelInfo } { + override getModel(): { id: string; info: ModelInfo } { if (this.client) { // Validate client properties const requiredProps = { diff --git a/src/api/transform/__tests__/vertex-gemini-format.test.ts b/src/api/transform/__tests__/vertex-gemini-format.test.ts new file mode 100644 index 0000000000..bcb26df099 --- /dev/null +++ b/src/api/transform/__tests__/vertex-gemini-format.test.ts @@ -0,0 +1,338 @@ +// npx jest src/api/transform/__tests__/vertex-gemini-format.test.ts + +import { Anthropic } from "@anthropic-ai/sdk" + +import { convertAnthropicMessageToVertexGemini } from "../vertex-gemini-format" + +describe("convertAnthropicMessageToVertexGemini", () => { + it("should convert a simple text message", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: "Hello, world!", + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [{ text: "Hello, world!" }], + }) + }) + + it("should convert assistant role to model role", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "assistant", + content: "I'm an assistant", + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "model", + parts: [{ text: "I'm an assistant" }], + }) + }) + + it("should convert a message with text blocks", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { type: "text", text: "First paragraph" }, + { type: "text", text: "Second paragraph" }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [{ text: "First paragraph" }, { text: "Second paragraph" }], + }) + }) + + it("should convert a message with an image", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { type: "text", text: "Check out this image:" }, + { + type: "image", + source: { + type: "base64", + media_type: "image/jpeg", + data: "base64encodeddata", + }, + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { text: "Check out this image:" }, + { + inlineData: { + data: "base64encodeddata", + mimeType: "image/jpeg", + }, + }, + ], + }) + }) + + it("should throw an error for unsupported image source type", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "image", + source: { + type: "url", // Not supported + url: "https://example.com/image.jpg", + } as any, + }, + ], + } + + expect(() => convertAnthropicMessageToVertexGemini(anthropicMessage)).toThrow("Unsupported image source type") + }) + + it("should convert a message with tool use", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "assistant", + content: [ + { type: "text", text: "Let me calculate that for you." }, + { + type: "tool_use", + id: "calc-123", + name: "calculator", + input: { operation: "add", numbers: [2, 3] }, + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "model", + parts: [ + { text: "Let me calculate that for you." }, + { + functionCall: { + name: "calculator", + args: { operation: "add", numbers: [2, 3] }, + }, + }, + ], + }) + }) + + it("should convert a message with tool result as string", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { type: "text", text: "Here's the result:" }, + { + type: "tool_result", + tool_use_id: "calculator-123", + content: "The result is 5", + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { text: "Here's the result:" }, + { + functionResponse: { + name: "calculator", + response: { + name: "calculator", + content: "The result is 5", + }, + }, + }, + ], + }) + }) + + it("should handle empty tool result content", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "calculator-123", + content: null as any, // Empty content + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + // Should skip the empty tool result + expect(result).toEqual({ + role: "user", + parts: [], + }) + }) + + it("should convert a message with tool result as array with text only", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "search-123", + content: [ + { type: "text", text: "First result" }, + { type: "text", text: "Second result" }, + ], + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { + functionResponse: { + name: "search", + response: { + name: "search", + content: "First result\n\nSecond result", + }, + }, + }, + ], + }) + }) + + it("should convert a message with tool result as array with text and images", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "search-123", + content: [ + { type: "text", text: "Search results:" }, + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "image1data", + }, + }, + { + type: "image", + source: { + type: "base64", + media_type: "image/jpeg", + data: "image2data", + }, + }, + ], + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { + functionResponse: { + name: "search", + response: { + name: "search", + content: "Search results:\n\n(See next part for image)", + }, + }, + }, + { + inlineData: { + data: "image1data", + mimeType: "image/png", + }, + }, + { + inlineData: { + data: "image2data", + mimeType: "image/jpeg", + }, + }, + ], + }) + }) + + it("should convert a message with tool result containing only images", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "imagesearch-123", + content: [ + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "onlyimagedata", + }, + }, + ], + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { + functionResponse: { + name: "imagesearch", + response: { + name: "imagesearch", + content: "\n\n(See next part for image)", + }, + }, + }, + { + inlineData: { + data: "onlyimagedata", + mimeType: "image/png", + }, + }, + ], + }) + }) + + it("should throw an error for unsupported content block type", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "unknown_type", // Unsupported type + data: "some data", + } as any, + ], + } + + expect(() => convertAnthropicMessageToVertexGemini(anthropicMessage)).toThrow( + "Unsupported content block type: unknown_type", + ) + }) +}) diff --git a/src/api/transform/vertex-gemini-format.ts b/src/api/transform/vertex-gemini-format.ts new file mode 100644 index 0000000000..75abb7d3be --- /dev/null +++ b/src/api/transform/vertex-gemini-format.ts @@ -0,0 +1,83 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import { Content, FunctionCallPart, FunctionResponsePart, InlineDataPart, Part, TextPart } from "@google-cloud/vertexai" + +function convertAnthropicContentToVertexGemini(content: Anthropic.Messages.MessageParam["content"]): Part[] { + if (typeof content === "string") { + return [{ text: content } as TextPart] + } + + return content.flatMap((block) => { + switch (block.type) { + case "text": + return { text: block.text } as TextPart + case "image": + if (block.source.type !== "base64") { + throw new Error("Unsupported image source type") + } + return { + inlineData: { + data: block.source.data, + mimeType: block.source.media_type, + }, + } as InlineDataPart + case "tool_use": + return { + functionCall: { + name: block.name, + args: block.input, + }, + } as FunctionCallPart + case "tool_result": + const name = block.tool_use_id.split("-")[0] + if (!block.content) { + return [] + } + if (typeof block.content === "string") { + return { + functionResponse: { + name, + response: { + name, + content: block.content, + }, + }, + } as FunctionResponsePart + } else { + // The only case when tool_result could be array is when the tool failed and we're providing ie user feedback potentially with images + const textParts = block.content.filter((part) => part.type === "text") + const imageParts = block.content.filter((part) => part.type === "image") + const text = textParts.length > 0 ? textParts.map((part) => part.text).join("\n\n") : "" + const imageText = imageParts.length > 0 ? "\n\n(See next part for image)" : "" + return [ + { + functionResponse: { + name, + response: { + name, + content: text + imageText, + }, + }, + } as FunctionResponsePart, + ...imageParts.map( + (part) => + ({ + inlineData: { + data: part.source.data, + mimeType: part.source.media_type, + }, + }) as InlineDataPart, + ), + ] + } + default: + throw new Error(`Unsupported content block type: ${(block as any).type}`) + } + }) +} + +export function convertAnthropicMessageToVertexGemini(message: Anthropic.Messages.MessageParam): Content { + return { + role: message.role === "assistant" ? "model" : "user", + parts: convertAnthropicContentToVertexGemini(message.content), + } +} diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 00897eecf4..c339e713c3 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -22,7 +22,7 @@ import { everyLineHasLineNumbers, truncateOutput, } from "../integrations/misc/extract-text" -import { TerminalManager } from "../integrations/terminal/TerminalManager" +import { TerminalManager, ExitCodeDetails } from "../integrations/terminal/TerminalManager" import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" import { listFiles } from "../services/glob/list-files" import { regexSearchFiles } from "../services/ripgrep" @@ -148,7 +148,8 @@ export class Cline { throw new Error("Either historyItem or task/images must be provided") } - this.taskId = crypto.randomUUID() + this.taskId = historyItem ? historyItem.id : crypto.randomUUID() + this.apiConfiguration = apiConfiguration this.api = buildApiHandler(apiConfiguration) this.terminalManager = new TerminalManager() @@ -161,10 +162,6 @@ export class Cline { this.diffViewProvider = new DiffViewProvider(cwd) this.enableCheckpoints = enableCheckpoints ?? false - if (historyItem) { - this.taskId = historyItem.id - } - // Initialize diffStrategy based on current state this.updateDiffStrategy(Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY)) @@ -834,10 +831,21 @@ export class Cline { }) let completed = false - process.once("completed", () => { + let exitDetails: ExitCodeDetails | undefined + process.once("completed", (output?: string) => { + // Use provided output if available, otherwise keep existing result. + if (output) { + lines = output.split("\n") + } completed = true }) + process.once("shell_execution_complete", (id: number, details: ExitCodeDetails) => { + if (id === terminalInfo.id) { + exitDetails = details + } + }) + process.once("no_shell_integration", async () => { await this.say("shell_integration_warning") }) @@ -869,7 +877,18 @@ export class Cline { } if (completed) { - return [false, `Command executed.${result.length > 0 ? `\nOutput:\n${result}` : ""}`] + let exitStatus = "No exit code available" + if (exitDetails !== undefined) { + if (exitDetails.signal) { + exitStatus = `Process terminated by signal ${exitDetails.signal} (${exitDetails.signalName})` + if (exitDetails.coreDumpPossible) { + exitStatus += " - core dump possible" + } + } else { + exitStatus = `Exit code: ${exitDetails.exitCode}` + } + } + return [false, `Command executed. ${exitStatus}${result.length > 0 ? `\nOutput:\n${result}` : ""}`] } else { return [ false, @@ -971,12 +990,12 @@ export class Cline { ? this.apiConfiguration.modelMaxTokens || modelInfo.maxTokens : modelInfo.maxTokens const contextWindow = modelInfo.contextWindow - - const trimmedMessages = truncateConversationIfNeeded({ + const trimmedMessages = await truncateConversationIfNeeded({ messages: this.apiConversationHistory, totalTokens, maxTokens, contextWindow, + apiHandler: this.api, }) if (trimmedMessages !== this.apiConversationHistory) { @@ -3315,7 +3334,7 @@ export class Cline { ) { const currentModeName = getModeBySlug(currentMode, customModes)?.name ?? currentMode const defaultModeName = getModeBySlug(defaultModeSlug, customModes)?.name ?? defaultModeSlug - details += `\n\nNOTE: You are currently in '${currentModeName}' mode which only allows read-only operations. To write files or execute commands, the user will need to switch to '${defaultModeName}' mode. Note that only the user can switch modes.` + details += `\n\nNOTE: You are currently in '${currentModeName}' mode, which does not allow write operations. To write files, the user will need to switch to a mode that supports file writing, such as '${defaultModeName}' mode.` } if (includeFileDetails) { diff --git a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap index b8050adb77..291745fcd1 100644 --- a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap +++ b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap @@ -3899,9 +3899,17 @@ USER'S CUSTOM INSTRUCTIONS The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines. Mode-specific Instructions: -Depending on the user's request, you may need to do some information gathering (for example using read_file or search_files) to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. (You can write the plan to a markdown file if it seems appropriate.) +1. Do some information gathering (for example using read_file or search_files) to get more context about the task. -Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. Finally once it seems like you've reached a good plan, use the switch_mode tool to request that the user switch to another mode to implement the solution. +2. You should also ask the user clarifying questions to get a better understanding of the task. + +3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer. + +4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. + +5. Once the user confirms the plan, ask them if they'd like you to write it to a markdown file. + +6. Use the switch_mode tool to request that the user switch to another mode to implement the solution. Rules: # Rules from .clinerules-architect: @@ -4176,7 +4184,7 @@ USER'S CUSTOM INSTRUCTIONS The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines. Mode-specific Instructions: -You can analyze code, explain concepts, and access external resources. Make sure to answer the user's questions and don't rush to switch to implementing code. +You can analyze code, explain concepts, and access external resources. Make sure to answer the user's questions and don't rush to switch to implementing code. Include Mermaid diagrams if they help make your response clearer. Rules: # Rules from .clinerules-ask: diff --git a/src/core/sliding-window/__tests__/sliding-window.test.ts b/src/core/sliding-window/__tests__/sliding-window.test.ts index cb897aa8cb..532d00067a 100644 --- a/src/core/sliding-window/__tests__/sliding-window.test.ts +++ b/src/core/sliding-window/__tests__/sliding-window.test.ts @@ -3,7 +3,35 @@ import { Anthropic } from "@anthropic-ai/sdk" import { ModelInfo } from "../../../shared/api" -import { truncateConversation, truncateConversationIfNeeded } from "../index" +import { ApiHandler } from "../../../api" +import { BaseProvider } from "../../../api/providers/base-provider" +import { TOKEN_BUFFER_PERCENTAGE } from "../index" +import { estimateTokenCount, truncateConversation, truncateConversationIfNeeded } from "../index" + +// Create a mock ApiHandler for testing +class MockApiHandler extends BaseProvider { + createMessage(): any { + throw new Error("Method not implemented.") + } + + getModel(): { id: string; info: ModelInfo } { + return { + id: "test-model", + info: { + contextWindow: 100000, + maxTokens: 50000, + supportsPromptCache: true, + supportsImages: false, + inputPrice: 0, + outputPrice: 0, + description: "Test model", + }, + } + } +} + +// Create a singleton instance for tests +const mockApiHandler = new MockApiHandler() /** * Tests for the truncateConversation function @@ -94,6 +122,296 @@ describe("truncateConversation", () => { }) }) +/** + * Tests for the estimateTokenCount function + */ +describe("estimateTokenCount", () => { + it("should return 0 for empty or undefined content", async () => { + expect(await estimateTokenCount([], mockApiHandler)).toBe(0) + // @ts-ignore - Testing with undefined + expect(await estimateTokenCount(undefined, mockApiHandler)).toBe(0) + }) + + it("should estimate tokens for text blocks", async () => { + const content: Array = [ + { type: "text", text: "This is a text block with 36 characters" }, + ] + + // With tiktoken, the exact token count may differ from character-based estimation + // Instead of expecting an exact number, we verify it's a reasonable positive number + const result = await estimateTokenCount(content, mockApiHandler) + expect(result).toBeGreaterThan(0) + + // We can also verify that longer text results in more tokens + const longerContent: Array = [ + { + type: "text", + text: "This is a longer text block with significantly more characters to encode into tokens", + }, + ] + const longerResult = await estimateTokenCount(longerContent, mockApiHandler) + expect(longerResult).toBeGreaterThan(result) + }) + + it("should estimate tokens for image blocks based on data size", async () => { + // Small image + const smallImage: Array = [ + { type: "image", source: { type: "base64", media_type: "image/jpeg", data: "small_dummy_data" } }, + ] + // Larger image with more data + const largerImage: Array = [ + { type: "image", source: { type: "base64", media_type: "image/png", data: "X".repeat(1000) } }, + ] + + // Verify the token count scales with the size of the image data + const smallImageTokens = await estimateTokenCount(smallImage, mockApiHandler) + const largerImageTokens = await estimateTokenCount(largerImage, mockApiHandler) + + // Small image should have some tokens + expect(smallImageTokens).toBeGreaterThan(0) + + // Larger image should have proportionally more tokens + expect(largerImageTokens).toBeGreaterThan(smallImageTokens) + + // Verify the larger image calculation matches our formula including the 50% fudge factor + expect(largerImageTokens).toBe(48) + }) + + it("should estimate tokens for mixed content blocks", async () => { + const content: Array = [ + { type: "text", text: "A text block with 30 characters" }, + { type: "image", source: { type: "base64", media_type: "image/jpeg", data: "dummy_data" } }, + { type: "text", text: "Another text with 24 chars" }, + ] + + // We know image tokens calculation should be consistent + const imageTokens = Math.ceil(Math.sqrt("dummy_data".length)) * 1.5 + + // With tiktoken, we can't predict exact text token counts, + // but we can verify the total is greater than just the image tokens + const result = await estimateTokenCount(content, mockApiHandler) + expect(result).toBeGreaterThan(imageTokens) + + // Also test against a version with only the image to verify text adds tokens + const imageOnlyContent: Array = [ + { type: "image", source: { type: "base64", media_type: "image/jpeg", data: "dummy_data" } }, + ] + const imageOnlyResult = await estimateTokenCount(imageOnlyContent, mockApiHandler) + expect(result).toBeGreaterThan(imageOnlyResult) + }) + + it("should handle empty text blocks", async () => { + const content: Array = [{ type: "text", text: "" }] + expect(await estimateTokenCount(content, mockApiHandler)).toBe(0) + }) + + it("should handle plain string messages", async () => { + const content = "This is a plain text message" + expect(await estimateTokenCount([{ type: "text", text: content }], mockApiHandler)).toBeGreaterThan(0) + }) +}) + +/** + * Tests for the truncateConversationIfNeeded function + */ +describe("truncateConversationIfNeeded", () => { + const createModelInfo = (contextWindow: number, maxTokens?: number): ModelInfo => ({ + contextWindow, + supportsPromptCache: true, + maxTokens, + }) + + const messages: Anthropic.Messages.MessageParam[] = [ + { role: "user", content: "First message" }, + { role: "assistant", content: "Second message" }, + { role: "user", content: "Third message" }, + { role: "assistant", content: "Fourth message" }, + { role: "user", content: "Fifth message" }, + ] + + it("should not truncate if tokens are below max tokens threshold", async () => { + const modelInfo = createModelInfo(100000, 30000) + const maxTokens = 100000 - 30000 // 70000 + const dynamicBuffer = modelInfo.contextWindow * TOKEN_BUFFER_PERCENTAGE // 10000 + const totalTokens = 70000 - dynamicBuffer - 1 // Just below threshold - buffer + + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + const result = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens, + contextWindow: modelInfo.contextWindow, + maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, + }) + expect(result).toEqual(messagesWithSmallContent) // No truncation occurs + }) + + it("should truncate if tokens are above max tokens threshold", async () => { + const modelInfo = createModelInfo(100000, 30000) + const maxTokens = 100000 - 30000 // 70000 + const totalTokens = 70001 // Above threshold + + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // When truncating, always uses 0.5 fraction + // With 4 messages after the first, 0.5 fraction means remove 2 messages + const expectedResult = [messagesWithSmallContent[0], messagesWithSmallContent[3], messagesWithSmallContent[4]] + + const result = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens, + contextWindow: modelInfo.contextWindow, + maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, + }) + expect(result).toEqual(expectedResult) + }) + + it("should work with non-prompt caching models the same as prompt caching models", async () => { + // The implementation no longer differentiates between prompt caching and non-prompt caching models + const modelInfo1 = createModelInfo(100000, 30000) + const modelInfo2 = createModelInfo(100000, 30000) + + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // Test below threshold + const belowThreshold = 69999 + const result1 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: belowThreshold, + contextWindow: modelInfo1.contextWindow, + maxTokens: modelInfo1.maxTokens, + apiHandler: mockApiHandler, + }) + + const result2 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: belowThreshold, + contextWindow: modelInfo2.contextWindow, + maxTokens: modelInfo2.maxTokens, + apiHandler: mockApiHandler, + }) + + expect(result1).toEqual(result2) + + // Test above threshold + const aboveThreshold = 70001 + const result3 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: aboveThreshold, + contextWindow: modelInfo1.contextWindow, + maxTokens: modelInfo1.maxTokens, + apiHandler: mockApiHandler, + }) + + const result4 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: aboveThreshold, + contextWindow: modelInfo2.contextWindow, + maxTokens: modelInfo2.maxTokens, + apiHandler: mockApiHandler, + }) + + expect(result3).toEqual(result4) + }) + + it("should consider incoming content when deciding to truncate", async () => { + const modelInfo = createModelInfo(100000, 30000) + const maxTokens = 30000 + const availableTokens = modelInfo.contextWindow - maxTokens + + // Test case 1: Small content that won't push us over the threshold + const smallContent = [{ type: "text" as const, text: "Small content" }] + const smallContentTokens = await estimateTokenCount(smallContent, mockApiHandler) + const messagesWithSmallContent: Anthropic.Messages.MessageParam[] = [ + ...messages.slice(0, -1), + { role: messages[messages.length - 1].role, content: smallContent }, + ] + + // Set base tokens so total is well below threshold + buffer even with small content added + const dynamicBuffer = modelInfo.contextWindow * TOKEN_BUFFER_PERCENTAGE + const baseTokensForSmall = availableTokens - smallContentTokens - dynamicBuffer - 10 + const resultWithSmall = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: baseTokensForSmall, + contextWindow: modelInfo.contextWindow, + maxTokens, + apiHandler: mockApiHandler, + }) + expect(resultWithSmall).toEqual(messagesWithSmallContent) // No truncation + + // Test case 2: Large content that will push us over the threshold + const largeContent = [ + { + type: "text" as const, + text: "A very large incoming message that would consume a significant number of tokens and push us over the threshold", + }, + ] + const largeContentTokens = await estimateTokenCount(largeContent, mockApiHandler) + const messagesWithLargeContent: Anthropic.Messages.MessageParam[] = [ + ...messages.slice(0, -1), + { role: messages[messages.length - 1].role, content: largeContent }, + ] + + // Set base tokens so we're just below threshold without content, but over with content + const baseTokensForLarge = availableTokens - Math.floor(largeContentTokens / 2) + const resultWithLarge = await truncateConversationIfNeeded({ + messages: messagesWithLargeContent, + totalTokens: baseTokensForLarge, + contextWindow: modelInfo.contextWindow, + maxTokens, + apiHandler: mockApiHandler, + }) + expect(resultWithLarge).not.toEqual(messagesWithLargeContent) // Should truncate + + // Test case 3: Very large content that will definitely exceed threshold + const veryLargeContent = [{ type: "text" as const, text: "X".repeat(1000) }] + const veryLargeContentTokens = await estimateTokenCount(veryLargeContent, mockApiHandler) + const messagesWithVeryLargeContent: Anthropic.Messages.MessageParam[] = [ + ...messages.slice(0, -1), + { role: messages[messages.length - 1].role, content: veryLargeContent }, + ] + + // Set base tokens so we're just below threshold without content + const baseTokensForVeryLarge = availableTokens - Math.floor(veryLargeContentTokens / 2) + const resultWithVeryLarge = await truncateConversationIfNeeded({ + messages: messagesWithVeryLargeContent, + totalTokens: baseTokensForVeryLarge, + contextWindow: modelInfo.contextWindow, + maxTokens, + apiHandler: mockApiHandler, + }) + expect(resultWithVeryLarge).not.toEqual(messagesWithVeryLargeContent) // Should truncate + }) + + it("should truncate if tokens are within TOKEN_BUFFER_PERCENTAGE of the threshold", async () => { + const modelInfo = createModelInfo(100000, 30000) + const maxTokens = 100000 - 30000 // 70000 + const dynamicBuffer = modelInfo.contextWindow * TOKEN_BUFFER_PERCENTAGE // 10% of 100000 = 10000 + const totalTokens = 70000 - dynamicBuffer + 1 // Just within the dynamic buffer of threshold (70000) + + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // When truncating, always uses 0.5 fraction + // With 4 messages after the first, 0.5 fraction means remove 2 messages + const expectedResult = [messagesWithSmallContent[0], messagesWithSmallContent[3], messagesWithSmallContent[4]] + + const result = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens, + contextWindow: modelInfo.contextWindow, + maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, + }) + expect(result).toEqual(expectedResult) + }) +}) + /** * Tests for the getMaxTokens function (private but tested through truncateConversationIfNeeded) */ @@ -114,192 +432,122 @@ describe("getMaxTokens", () => { { role: "user", content: "Fifth message" }, ] - it("should use maxTokens as buffer when specified", () => { + it("should use maxTokens as buffer when specified", async () => { const modelInfo = createModelInfo(100000, 50000) // Max tokens = 100000 - 50000 = 50000 - // Below max tokens - no truncation - const result1 = truncateConversationIfNeeded({ - messages, - totalTokens: 49999, + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // Account for the dynamic buffer which is 10% of context window (10,000 tokens) + // Below max tokens and buffer - no truncation + const result1 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 39999, // Well below threshold + dynamic buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate - const result2 = truncateConversationIfNeeded({ - messages, - totalTokens: 50001, + const result2 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 50001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) - it("should use 20% of context window as buffer when maxTokens is undefined", () => { + it("should use 20% of context window as buffer when maxTokens is undefined", async () => { const modelInfo = createModelInfo(100000, undefined) // Max tokens = 100000 - (100000 * 0.2) = 80000 - // Below max tokens - no truncation - const result1 = truncateConversationIfNeeded({ - messages, - totalTokens: 79999, + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // Account for the dynamic buffer which is 10% of context window (10,000 tokens) + // Below max tokens and buffer - no truncation + const result1 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 69999, // Well below threshold + dynamic buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate - const result2 = truncateConversationIfNeeded({ - messages, - totalTokens: 80001, + const result2 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 80001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) - it("should handle small context windows appropriately", () => { + it("should handle small context windows appropriately", async () => { const modelInfo = createModelInfo(50000, 10000) // Max tokens = 50000 - 10000 = 40000 - // Below max tokens - no truncation - const result1 = truncateConversationIfNeeded({ - messages, - totalTokens: 39999, + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // Below max tokens and buffer - no truncation + const result1 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 34999, // Well below threshold + buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate - const result2 = truncateConversationIfNeeded({ - messages, - totalTokens: 40001, + const result2 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 40001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) - it("should handle large context windows appropriately", () => { + it("should handle large context windows appropriately", async () => { const modelInfo = createModelInfo(200000, 30000) // Max tokens = 200000 - 30000 = 170000 - // Below max tokens - no truncation - const result1 = truncateConversationIfNeeded({ - messages, - totalTokens: 169999, + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // Account for the dynamic buffer which is 10% of context window (20,000 tokens for this test) + // Below max tokens and buffer - no truncation + const result1 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 149999, // Well below threshold + dynamic buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate - const result2 = truncateConversationIfNeeded({ - messages, - totalTokens: 170001, + const result2 = await truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: 170001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, + apiHandler: mockApiHandler, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) }) - -/** - * Tests for the truncateConversationIfNeeded function - */ -describe("truncateConversationIfNeeded", () => { - const createModelInfo = (contextWindow: number, supportsPromptCache: boolean, maxTokens?: number): ModelInfo => ({ - contextWindow, - supportsPromptCache, - maxTokens, - }) - - const messages: Anthropic.Messages.MessageParam[] = [ - { role: "user", content: "First message" }, - { role: "assistant", content: "Second message" }, - { role: "user", content: "Third message" }, - { role: "assistant", content: "Fourth message" }, - { role: "user", content: "Fifth message" }, - ] - - it("should not truncate if tokens are below max tokens threshold", () => { - const modelInfo = createModelInfo(100000, true, 30000) - const maxTokens = 100000 - 30000 // 70000 - const totalTokens = 69999 // Below threshold - - const result = truncateConversationIfNeeded({ - messages, - totalTokens, - contextWindow: modelInfo.contextWindow, - maxTokens: modelInfo.maxTokens, - }) - expect(result).toEqual(messages) // No truncation occurs - }) - - it("should truncate if tokens are above max tokens threshold", () => { - const modelInfo = createModelInfo(100000, true, 30000) - const maxTokens = 100000 - 30000 // 70000 - const totalTokens = 70001 // Above threshold - - // When truncating, always uses 0.5 fraction - // With 4 messages after the first, 0.5 fraction means remove 2 messages - const expectedResult = [messages[0], messages[3], messages[4]] - - const result = truncateConversationIfNeeded({ - messages, - totalTokens, - contextWindow: modelInfo.contextWindow, - maxTokens: modelInfo.maxTokens, - }) - expect(result).toEqual(expectedResult) - }) - - it("should work with non-prompt caching models the same as prompt caching models", () => { - // The implementation no longer differentiates between prompt caching and non-prompt caching models - const modelInfo1 = createModelInfo(100000, true, 30000) - const modelInfo2 = createModelInfo(100000, false, 30000) - - // Test below threshold - const belowThreshold = 69999 - expect( - truncateConversationIfNeeded({ - messages, - totalTokens: belowThreshold, - contextWindow: modelInfo1.contextWindow, - maxTokens: modelInfo1.maxTokens, - }), - ).toEqual( - truncateConversationIfNeeded({ - messages, - totalTokens: belowThreshold, - contextWindow: modelInfo2.contextWindow, - maxTokens: modelInfo2.maxTokens, - }), - ) - - // Test above threshold - const aboveThreshold = 70001 - expect( - truncateConversationIfNeeded({ - messages, - totalTokens: aboveThreshold, - contextWindow: modelInfo1.contextWindow, - maxTokens: modelInfo1.maxTokens, - }), - ).toEqual( - truncateConversationIfNeeded({ - messages, - totalTokens: aboveThreshold, - contextWindow: modelInfo2.contextWindow, - maxTokens: modelInfo2.maxTokens, - }), - ) - }) -}) diff --git a/src/core/sliding-window/index.ts b/src/core/sliding-window/index.ts index 8b646f933b..67c0028fab 100644 --- a/src/core/sliding-window/index.ts +++ b/src/core/sliding-window/index.ts @@ -1,4 +1,25 @@ import { Anthropic } from "@anthropic-ai/sdk" +import { ApiHandler } from "../../api" + +/** + * Default percentage of the context window to use as a buffer when deciding when to truncate + */ +export const TOKEN_BUFFER_PERCENTAGE = 0.1 + +/** + * Counts tokens for user content using the provider's token counting implementation. + * + * @param {Array} content - The content to count tokens for + * @param {ApiHandler} apiHandler - The API handler to use for token counting + * @returns {Promise} A promise resolving to the token count + */ +export async function estimateTokenCount( + content: Array, + apiHandler: ApiHandler, +): Promise { + if (!content || content.length === 0) return 0 + return apiHandler.countTokens(content) +} /** * Truncates a conversation by removing a fraction of the messages. @@ -25,12 +46,13 @@ export function truncateConversation( /** * Conditionally truncates the conversation messages if the total token count - * exceeds the model's limit. + * exceeds the model's limit, considering the size of incoming content. * * @param {Anthropic.Messages.MessageParam[]} messages - The conversation messages. - * @param {number} totalTokens - The total number of tokens in the conversation. + * @param {number} totalTokens - The total number of tokens in the conversation (excluding the last user message). * @param {number} contextWindow - The context window size. * @param {number} maxTokens - The maximum number of tokens allowed. + * @param {ApiHandler} apiHandler - The API handler to use for token counting. * @returns {Anthropic.Messages.MessageParam[]} The original or truncated conversation messages. */ @@ -39,14 +61,40 @@ type TruncateOptions = { totalTokens: number contextWindow: number maxTokens?: number + apiHandler: ApiHandler } -export function truncateConversationIfNeeded({ +/** + * Conditionally truncates the conversation messages if the total token count + * exceeds the model's limit, considering the size of incoming content. + * + * @param {TruncateOptions} options - The options for truncation + * @returns {Promise} The original or truncated conversation messages. + */ +export async function truncateConversationIfNeeded({ messages, totalTokens, contextWindow, maxTokens, -}: TruncateOptions): Anthropic.Messages.MessageParam[] { - const allowedTokens = contextWindow - (maxTokens || contextWindow * 0.2) - return totalTokens < allowedTokens ? messages : truncateConversation(messages, 0.5) + apiHandler, +}: TruncateOptions): Promise { + // Calculate the maximum tokens reserved for response + const reservedTokens = maxTokens || contextWindow * 0.2 + + // Estimate tokens for the last message (which is always a user message) + const lastMessage = messages[messages.length - 1] + const lastMessageContent = lastMessage.content + const lastMessageTokens = Array.isArray(lastMessageContent) + ? await estimateTokenCount(lastMessageContent, apiHandler) + : await estimateTokenCount([{ type: "text", text: lastMessageContent as string }], apiHandler) + + // Calculate total effective tokens (totalTokens never includes the last message) + const effectiveTokens = totalTokens + lastMessageTokens + + // Calculate available tokens for conversation history + // Truncate if we're within TOKEN_BUFFER_PERCENTAGE of the context window + const allowedTokens = contextWindow * (1 - TOKEN_BUFFER_PERCENTAGE) - reservedTokens + + // Determine if truncation is needed and apply if necessary + return effectiveTokens > allowedTokens ? truncateConversation(messages, 0.5) : messages } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index a214471653..e75be17ac3 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -519,7 +519,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
- + ` @@ -2459,6 +2459,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { autoApprovalEnabled: autoApprovalEnabled ?? false, customModes, maxOpenTabsContext: maxOpenTabsContext ?? 20, + openRouterUseMiddleOutTransform: openRouterUseMiddleOutTransform ?? true, } } diff --git a/src/exports/cline.d.ts b/src/exports/cline.d.ts index fcf93fc10d..e529947b6b 100644 --- a/src/exports/cline.d.ts +++ b/src/exports/cline.d.ts @@ -40,3 +40,96 @@ export interface ClineAPI { */ sidebarProvider: ClineSidebarProvider } + +export interface ClineProvider { + readonly context: vscode.ExtensionContext + readonly viewLaunched: boolean + readonly messages: ClineMessage[] + + /** + * Resolves the webview view for the provider + * @param webviewView The webview view or panel to resolve + */ + resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel): Promise + + /** + * Initializes Cline with a task + */ + initClineWithTask(task?: string, images?: string[]): Promise + + /** + * Initializes Cline with a history item + */ + initClineWithHistoryItem(historyItem: HistoryItem): Promise + + /** + * Posts a message to the webview + */ + postMessageToWebview(message: ExtensionMessage): Promise + + /** + * Handles mode switching + */ + handleModeSwitch(newMode: Mode): Promise + + /** + * Updates custom instructions + */ + updateCustomInstructions(instructions?: string): Promise + + /** + * Cancels the current task + */ + cancelTask(): Promise + + /** + * Clears the current task + */ + clearTask(): Promise + + /** + * Gets the current state + */ + getState(): Promise + + /** + * Updates a value in the global state + * @param key The key to update + * @param value The value to set + */ + updateGlobalState(key: GlobalStateKey, value: any): Promise + + /** + * Gets a value from the global state + * @param key The key to get + */ + getGlobalState(key: GlobalStateKey): Promise + + /** + * Stores a secret value in secure storage + * @param key The key to store the secret under + * @param value The secret value to store, or undefined to remove the secret + */ + storeSecret(key: SecretKey, value?: string): Promise + + /** + * Retrieves a secret value from secure storage + * @param key The key of the secret to retrieve + */ + getSecret(key: SecretKey): Promise + + /** + * Resets the state + */ + resetState(): Promise + + /** + * Logs a message + */ + log(message: string): void + + /** + * Disposes of the provider + */ + dispose(): Promise +} diff --git a/src/integrations/terminal/TerminalManager.ts b/src/integrations/terminal/TerminalManager.ts index d5496e20fb..a55f7867d4 100644 --- a/src/integrations/terminal/TerminalManager.ts +++ b/src/integrations/terminal/TerminalManager.ts @@ -70,6 +70,15 @@ Interestingly, some environments like Cursor enable these APIs even without the This approach allows us to leverage advanced features when available while ensuring broad compatibility. */ declare module "vscode" { + // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L7442 + // interface Terminal { + // shellIntegration?: { + // cwd?: vscode.Uri + // executeCommand?: (command: string) => { + // read: () => AsyncIterable + // } + // } + // } // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L10794 interface Window { onDidStartTerminalShellExecution?: ( @@ -77,17 +86,19 @@ declare module "vscode" { thisArgs?: any, disposables?: vscode.Disposable[], ) => vscode.Disposable + onDidEndTerminalShellExecution?: ( + listener: (e: { terminal: vscode.Terminal; exitCode?: number; shellType?: string }) => any, + thisArgs?: any, + disposables?: vscode.Disposable[], + ) => vscode.Disposable } } -// Extend the Terminal type to include our custom properties -type ExtendedTerminal = vscode.Terminal & { - shellIntegration?: { - cwd?: vscode.Uri - executeCommand?: (command: string) => { - read: () => AsyncIterable - } - } +export interface ExitCodeDetails { + exitCode: number | undefined + signal?: number | undefined + signalName?: string + coreDumpPossible?: boolean } export class TerminalManager { @@ -95,18 +106,156 @@ export class TerminalManager { private processes: Map = new Map() private disposables: vscode.Disposable[] = [] + private interpretExitCode(exitCode: number | undefined): ExitCodeDetails { + if (exitCode === undefined) { + return { exitCode } + } + + if (exitCode <= 128) { + return { exitCode } + } + + const signal = exitCode - 128 + const signals: Record = { + // Standard signals + 1: "SIGHUP", + 2: "SIGINT", + 3: "SIGQUIT", + 4: "SIGILL", + 5: "SIGTRAP", + 6: "SIGABRT", + 7: "SIGBUS", + 8: "SIGFPE", + 9: "SIGKILL", + 10: "SIGUSR1", + 11: "SIGSEGV", + 12: "SIGUSR2", + 13: "SIGPIPE", + 14: "SIGALRM", + 15: "SIGTERM", + 16: "SIGSTKFLT", + 17: "SIGCHLD", + 18: "SIGCONT", + 19: "SIGSTOP", + 20: "SIGTSTP", + 21: "SIGTTIN", + 22: "SIGTTOU", + 23: "SIGURG", + 24: "SIGXCPU", + 25: "SIGXFSZ", + 26: "SIGVTALRM", + 27: "SIGPROF", + 28: "SIGWINCH", + 29: "SIGIO", + 30: "SIGPWR", + 31: "SIGSYS", + + // Real-time signals base + 34: "SIGRTMIN", + + // SIGRTMIN+n signals + 35: "SIGRTMIN+1", + 36: "SIGRTMIN+2", + 37: "SIGRTMIN+3", + 38: "SIGRTMIN+4", + 39: "SIGRTMIN+5", + 40: "SIGRTMIN+6", + 41: "SIGRTMIN+7", + 42: "SIGRTMIN+8", + 43: "SIGRTMIN+9", + 44: "SIGRTMIN+10", + 45: "SIGRTMIN+11", + 46: "SIGRTMIN+12", + 47: "SIGRTMIN+13", + 48: "SIGRTMIN+14", + 49: "SIGRTMIN+15", + + // SIGRTMAX-n signals + 50: "SIGRTMAX-14", + 51: "SIGRTMAX-13", + 52: "SIGRTMAX-12", + 53: "SIGRTMAX-11", + 54: "SIGRTMAX-10", + 55: "SIGRTMAX-9", + 56: "SIGRTMAX-8", + 57: "SIGRTMAX-7", + 58: "SIGRTMAX-6", + 59: "SIGRTMAX-5", + 60: "SIGRTMAX-4", + 61: "SIGRTMAX-3", + 62: "SIGRTMAX-2", + 63: "SIGRTMAX-1", + 64: "SIGRTMAX", + } + + // These signals may produce core dumps: + // SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV + const coreDumpPossible = new Set([3, 4, 6, 7, 8, 11]) + + return { + exitCode, + signal, + signalName: signals[signal] || `Unknown Signal (${signal})`, + coreDumpPossible: coreDumpPossible.has(signal), + } + } + constructor() { - let disposable: vscode.Disposable | undefined + let startDisposable: vscode.Disposable | undefined + let endDisposable: vscode.Disposable | undefined try { - disposable = (vscode.window as vscode.Window).onDidStartTerminalShellExecution?.(async (e) => { - // Creating a read stream here results in a more consistent output. This is most obvious when running the `date` command. - e?.execution?.read() + // onDidStartTerminalShellExecution + startDisposable = (vscode.window as vscode.Window).onDidStartTerminalShellExecution?.(async (e) => { + // Get a handle to the stream as early as possible: + const stream = e?.execution.read() + const terminalInfo = TerminalRegistry.getTerminalInfoByTerminal(e.terminal) + if (stream && terminalInfo) { + const process = this.processes.get(terminalInfo.id) + if (process) { + terminalInfo.stream = stream + terminalInfo.running = true + terminalInfo.streamClosed = false + process.emit("stream_available", terminalInfo.id, stream) + } + } else { + console.error("[TerminalManager] Stream failed, not registered for terminal") + } + + console.info("[TerminalManager] Shell execution started:", { + hasExecution: !!e?.execution, + command: e?.execution?.commandLine?.value, + terminalId: terminalInfo?.id, + }) + }) + + // onDidEndTerminalShellExecution + endDisposable = (vscode.window as vscode.Window).onDidEndTerminalShellExecution?.(async (e) => { + const exitDetails = this.interpretExitCode(e?.exitCode) + console.info("[TerminalManager] Shell execution ended:", { + ...exitDetails, + }) + + // Signal completion to any waiting processes + for (const id of this.terminalIds) { + const info = TerminalRegistry.getTerminal(id) + if (info && info.terminal === e.terminal) { + info.running = false + const process = this.processes.get(id) + if (process) { + process.emit("shell_execution_complete", id, exitDetails) + } + break + } + } }) } catch (error) { - // console.error("Error setting up onDidEndTerminalShellExecution", error) + console.error("[TerminalManager] Error setting up shell execution handlers:", error) } - if (disposable) { - this.disposables.push(disposable) + if (startDisposable) { + this.disposables.push(startDisposable) + } + if (endDisposable) { + this.disposables.push(endDisposable) } } @@ -140,19 +289,16 @@ export class TerminalManager { }) // if shell integration is already active, run the command immediately - const terminal = terminalInfo.terminal as ExtendedTerminal - if (terminal.shellIntegration) { + if (terminalInfo.terminal.shellIntegration) { process.waitForShellIntegration = false - process.run(terminal, command) + process.run(terminalInfo.terminal, command) } else { // docs recommend waiting 3s for shell integration to activate - pWaitFor(() => (terminalInfo.terminal as ExtendedTerminal).shellIntegration !== undefined, { - timeout: 4000, - }).finally(() => { + pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, { timeout: 4000 }).finally(() => { const existingProcess = this.processes.get(terminalInfo.id) if (existingProcess && existingProcess.waitForShellIntegration) { existingProcess.waitForShellIntegration = false - existingProcess.run(terminal, command) + existingProcess.run(terminalInfo.terminal, command) } }) } @@ -168,8 +314,7 @@ export class TerminalManager { if (t.busy) { return false } - const terminal = t.terminal as ExtendedTerminal - const terminalCwd = terminal.shellIntegration?.cwd // one of cline's commands could have changed the cwd of the terminal + const terminalCwd = t.terminal.shellIntegration?.cwd // one of cline's commands could have changed the cwd of the terminal if (!terminalCwd) { return false } diff --git a/src/integrations/terminal/TerminalProcess.ts b/src/integrations/terminal/TerminalProcess.ts index 5597350db3..99ef215e78 100644 --- a/src/integrations/terminal/TerminalProcess.ts +++ b/src/integrations/terminal/TerminalProcess.ts @@ -1,13 +1,24 @@ import { EventEmitter } from "events" import stripAnsi from "strip-ansi" import * as vscode from "vscode" +import { inspect } from "util" + +import { ExitCodeDetails } from "./TerminalManager" +import { TerminalInfo, TerminalRegistry } from "./TerminalRegistry" export interface TerminalProcessEvents { line: [line: string] continue: [] - completed: [] + completed: [output?: string] error: [error: Error] no_shell_integration: [] + /** + * Emitted when a shell execution completes + * @param id The terminal ID + * @param exitDetails Contains exit code and signal information if process was terminated by signal + */ + shell_execution_complete: [id: number, exitDetails: ExitCodeDetails] + stream_available: [id: number, stream: AsyncIterable] } // how long to wait after a process outputs anything before we consider it "cool" again @@ -17,104 +28,99 @@ const PROCESS_HOT_TIMEOUT_COMPILING = 15_000 export class TerminalProcess extends EventEmitter { waitForShellIntegration: boolean = true private isListening: boolean = true - private buffer: string = "" + private terminalInfo: TerminalInfo | undefined + private lastEmitTime_ms: number = 0 private fullOutput: string = "" private lastRetrievedIndex: number = 0 isHot: boolean = false private hotTimer: NodeJS.Timeout | null = null - // constructor() { - // super() - async run(terminal: vscode.Terminal, command: string) { if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) { - const execution = terminal.shellIntegration.executeCommand(command) - const stream = execution.read() - // todo: need to handle errors - let isFirstChunk = true - let didOutputNonCommand = false - let didEmitEmptyLine = false + // Get terminal info to access stream + const terminalInfo = TerminalRegistry.getTerminalInfoByTerminal(terminal) + if (!terminalInfo) { + console.error("[TerminalProcess] Terminal not found in registry") + this.emit("no_shell_integration") + this.emit("completed") + this.emit("continue") + return + } + + // When executeCommand() is called, onDidStartTerminalShellExecution will fire in TerminalManager + // which creates a new stream via execution.read() and emits 'stream_available' + const streamAvailable = new Promise>((resolve) => { + this.once("stream_available", (id: number, stream: AsyncIterable) => { + if (id === terminalInfo.id) { + resolve(stream) + } + }) + }) + + // Create promise that resolves when shell execution completes for this terminal + const shellExecutionComplete = new Promise((resolve) => { + this.once("shell_execution_complete", (id: number, exitDetails: ExitCodeDetails) => { + if (id === terminalInfo.id) { + resolve(exitDetails) + } + }) + }) + + // getUnretrievedOutput needs to know if streamClosed, so store this for later + this.terminalInfo = terminalInfo + + // Execute command + terminal.shellIntegration.executeCommand(command) + this.isHot = true + + // Wait for stream to be available + const stream = await streamAvailable + + let preOutput = "" + let commandOutputStarted = false + + /* + * Extract clean output from raw accumulated output. FYI: + * ]633 is a custom sequence number used by VSCode shell integration: + * - OSC 633 ; A ST - Mark prompt start + * - OSC 633 ; B ST - Mark prompt end + * - OSC 633 ; C ST - Mark pre-execution (start of command output) + * - OSC 633 ; D [; ] ST - Mark execution finished with optional exit code + * - OSC 633 ; E ; [; ] ST - Explicitly set command line with optional nonce + */ + + // Process stream data for await (let data of stream) { - // 1. Process chunk and remove artifacts - if (isFirstChunk) { - /* - The first chunk we get from this stream needs to be processed to be more human readable, ie remove vscode's custom escape sequences and identifiers, removing duplicate first char bug, etc. - */ - - // bug where sometimes the command output makes its way into vscode shell integration metadata - /* - ]633 is a custom sequence number used by VSCode shell integration: - - OSC 633 ; A ST - Mark prompt start - - OSC 633 ; B ST - Mark prompt end - - OSC 633 ; C ST - Mark pre-execution (start of command output) - - OSC 633 ; D [; ] ST - Mark execution finished with optional exit code - - OSC 633 ; E ; [; ] ST - Explicitly set command line with optional nonce - */ - // if you print this data you might see something like "eecho hello worldo hello world;5ba85d14-e92a-40c4-b2fd-71525581eeb0]633;C" but this is actually just a bunch of escape sequences, ignore up to the first ;C - /* ddateb15026-6a64-40db-b21f-2a621a9830f0]633;CTue Sep 17 06:37:04 EDT 2024 % ]633;D;0]633;P;Cwd=/Users/saoud/Repositories/test */ - // Gets output between ]633;C (command start) and ]633;D (command end) - const outputBetweenSequences = this.removeLastLineArtifacts( - data.match(/\]633;C([\s\S]*?)\]633;D/)?.[1] || "", - ).trim() - - // Once we've retrieved any potential output between sequences, we can remove everything up to end of the last sequence - // https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st - const vscodeSequenceRegex = /\x1b\]633;.[^\x07]*\x07/g - const lastMatch = [...data.matchAll(vscodeSequenceRegex)].pop() - if (lastMatch && lastMatch.index !== undefined) { - data = data.slice(lastMatch.index + lastMatch[0].length) + // Check for command output start marker + if (!commandOutputStarted) { + preOutput += data + const match = this.matchAfterVsceStartMarkers(data) + if (match !== undefined) { + commandOutputStarted = true + data = match + this.fullOutput = "" // Reset fullOutput when command actually starts + } else { + continue } - // Place output back after removing vscode sequences - if (outputBetweenSequences) { - data = outputBetweenSequences + "\n" + data - } - // remove ansi - data = stripAnsi(data) - // Split data by newlines - let lines = data ? data.split("\n") : [] - // Remove non-human readable characters from the first line - if (lines.length > 0) { - lines[0] = lines[0].replace(/[^\x20-\x7E]/g, "") - } - // Check if first two characters are the same, if so remove the first character - if (lines.length > 0 && lines[0].length >= 2 && lines[0][0] === lines[0][1]) { - lines[0] = lines[0].slice(1) - } - // Remove everything up to the first alphanumeric character for first two lines - if (lines.length > 0) { - lines[0] = lines[0].replace(/^[^a-zA-Z0-9]*/, "") - } - if (lines.length > 1) { - lines[1] = lines[1].replace(/^[^a-zA-Z0-9]*/, "") - } - // Join lines back - data = lines.join("\n") - isFirstChunk = false - } else { - data = stripAnsi(data) } - // first few chunks could be the command being echoed back, so we must ignore - // note this means that 'echo' commands wont work - if (!didOutputNonCommand) { - const lines = data.split("\n") - for (let i = 0; i < lines.length; i++) { - if (command.includes(lines[i].trim())) { - lines.splice(i, 1) - i-- // Adjust index after removal - } else { - didOutputNonCommand = true - break - } - } - data = lines.join("\n") + // Command output started, accumulate data without filtering. + // notice to future programmers: do not add escape sequence + // filtering here: fullOutput cannot change in length (see getUnretrievedOutput), + // and chunks may not be complete so you cannot rely on detecting or removing escape sequences mid-stream. + this.fullOutput += data + + // For non-immediately returning commands we want to show loading spinner + // right away but this wouldnt happen until it emits a line break, so + // as soon as we get any output we emit to let webview know to show spinner + const now = Date.now() + if (this.isListening && (now - this.lastEmitTime_ms > 100 || this.lastEmitTime_ms === 0)) { + this.emitRemainingBufferIfListening() + this.lastEmitTime_ms = now } - // FIXME: right now it seems that data chunks returned to us from the shell integration stream contains random commas, which from what I can tell is not the expected behavior. There has to be a better solution here than just removing all commas. - data = data.replace(/,/g, "") - - // 2. Set isHot depending on the command - // Set to hot to stall API requests until terminal is cool again + // 2. Set isHot depending on the command. + // This stalls API requests until terminal is cool again. this.isHot = true if (this.hotTimer) { clearTimeout(this.hotTimer) @@ -144,21 +150,37 @@ export class TerminalProcess extends EventEmitter { }, isCompiling ? PROCESS_HOT_TIMEOUT_COMPILING : PROCESS_HOT_TIMEOUT_NORMAL, ) - - // For non-immediately returning commands we want to show loading spinner right away but this wouldnt happen until it emits a line break, so as soon as we get any output we emit "" to let webview know to show spinner - if (!didEmitEmptyLine && !this.fullOutput && data) { - this.emit("line", "") // empty line to indicate start of command output stream - didEmitEmptyLine = true - } - - this.fullOutput += data - if (this.isListening) { - this.emitIfEol(data) - this.lastRetrievedIndex = this.fullOutput.length - this.buffer.length - } } - this.emitRemainingBufferIfListening() + // Set streamClosed immediately after stream ends + if (this.terminalInfo) { + this.terminalInfo.streamClosed = true + } + + // Wait for shell execution to complete and handle exit details + const exitDetails = await shellExecutionComplete + this.isHot = false + + if (commandOutputStarted) { + // Emit any remaining output before completing + this.emitRemainingBufferIfListening() + } else { + console.error( + "[Terminal Process] VSCE output start escape sequence (]633;C or ]133;C) not received! VSCE Bug? preOutput: " + + inspect(preOutput, { colors: false, breakLength: Infinity }), + ) + } + + // console.debug("[Terminal Process] raw output: " + inspect(output, { colors: false, breakLength: Infinity })) + + // fullOutput begins after C marker so we only need to trim off D marker + // (if D exists, see VSCode bug# 237208): + const match = this.matchBeforeVsceEndMarkers(this.fullOutput) + if (match !== undefined) { + this.fullOutput = match + } + + // console.debug(`[Terminal Process] processed output via ${matchSource}: ` + inspect(output, { colors: false, breakLength: Infinity })) // for now we don't want this delaying requests since we don't send diagnostics automatically anymore (previous: "even though the command is finished, we still want to consider it 'hot' in case so that api request stalls to let diagnostics catch up") if (this.hotTimer) { @@ -166,7 +188,7 @@ export class TerminalProcess extends EventEmitter { } this.isHot = false - this.emit("completed") + this.emit("completed", this.removeEscapeSequences(this.fullOutput)) this.emit("continue") } else { terminal.sendText(command, true) @@ -182,29 +204,12 @@ export class TerminalProcess extends EventEmitter { } } - // Inspired by https://github.com/sindresorhus/execa/blob/main/lib/transform/split.js - private emitIfEol(chunk: string) { - this.buffer += chunk - let lineEndIndex: number - while ((lineEndIndex = this.buffer.indexOf("\n")) !== -1) { - let line = this.buffer.slice(0, lineEndIndex).trimEnd() // removes trailing \r - // Remove \r if present (for Windows-style line endings) - // if (line.endsWith("\r")) { - // line = line.slice(0, -1) - // } - this.emit("line", line) - this.buffer = this.buffer.slice(lineEndIndex + 1) - } - } - private emitRemainingBufferIfListening() { - if (this.buffer && this.isListening) { - const remainingBuffer = this.removeLastLineArtifacts(this.buffer) - if (remainingBuffer) { + if (this.isListening) { + const remainingBuffer = this.getUnretrievedOutput() + if (remainingBuffer !== "") { this.emit("line", remainingBuffer) } - this.buffer = "" - this.lastRetrievedIndex = this.fullOutput.length } } @@ -215,22 +220,180 @@ export class TerminalProcess extends EventEmitter { this.emit("continue") } + // Returns complete lines with their carriage returns. + // The final line may lack a carriage return if the program didn't send one. getUnretrievedOutput(): string { - const unretrieved = this.fullOutput.slice(this.lastRetrievedIndex) - this.lastRetrievedIndex = this.fullOutput.length - return this.removeLastLineArtifacts(unretrieved) + // Get raw unretrieved output + let outputToProcess = this.fullOutput.slice(this.lastRetrievedIndex) + + // Check for VSCE command end markers + const index633 = outputToProcess.indexOf("\x1b]633;D") + const index133 = outputToProcess.indexOf("\x1b]133;D") + let endIndex = -1 + + if (index633 !== -1 && index133 !== -1) { + endIndex = Math.min(index633, index133) + } else if (index633 !== -1) { + endIndex = index633 + } else if (index133 !== -1) { + endIndex = index133 + } + + // If no end markers were found yet (possibly due to VSCode bug#237208): + // For active streams: return only complete lines (up to last \n). + // For closed streams: return all remaining content. + if (endIndex === -1) { + if (!this.terminalInfo?.streamClosed) { + // Stream still running - only process complete lines + endIndex = outputToProcess.lastIndexOf("\n") + if (endIndex === -1) { + // No complete lines + return "" + } + + // Include carriage return + endIndex++ + } else { + // Stream closed - process all remaining output + endIndex = outputToProcess.length + } + } + + // Update index and slice output + this.lastRetrievedIndex += endIndex + outputToProcess = outputToProcess.slice(0, endIndex) + + // Clean and return output + return this.removeEscapeSequences(outputToProcess) } - // some processing to remove artifacts like '%' at the end of the buffer (it seems that since vsode uses % at the beginning of newlines in terminal, it makes its way into the stream) - // This modification will remove '%', '$', '#', or '>' followed by optional whitespace - removeLastLineArtifacts(output: string) { - const lines = output.trimEnd().split("\n") - if (lines.length > 0) { - const lastLine = lines[lines.length - 1] - // Remove prompt characters and trailing whitespace from the last line - lines[lines.length - 1] = lastLine.replace(/[%$#>]\s*$/, "") + private stringIndexMatch( + data: string, + prefix?: string, + suffix?: string, + bell: string = "\x07", + ): string | undefined { + let startIndex: number + let endIndex: number + let prefixLength: number + + if (prefix === undefined) { + startIndex = 0 + prefixLength = 0 + } else { + startIndex = data.indexOf(prefix) + if (startIndex === -1) { + return undefined + } + if (bell.length > 0) { + // Find the bell character after the prefix + const bellIndex = data.indexOf(bell, startIndex + prefix.length) + if (bellIndex === -1) { + return undefined + } + + const distanceToBell = bellIndex - startIndex + + prefixLength = distanceToBell + bell.length + } else { + prefixLength = prefix.length + } } - return lines.join("\n").trimEnd() + + const contentStart = startIndex + prefixLength + + if (suffix === undefined) { + // When suffix is undefined, match to end + endIndex = data.length + } else { + endIndex = data.indexOf(suffix, contentStart) + if (endIndex === -1) { + return undefined + } + } + + return data.slice(contentStart, endIndex) + } + + // Removes ANSI escape sequences and VSCode-specific terminal control codes from output. + // While stripAnsi handles most ANSI codes, VSCode's shell integration adds custom + // escape sequences (OSC 633) that need special handling. These sequences control + // terminal features like marking command start/end and setting prompts. + // + // This method could be extended to handle other escape sequences, but any additions + // should be carefully considered to ensure they only remove control codes and don't + // alter the actual content or behavior of the output stream. + private removeEscapeSequences(str: string): string { + return stripAnsi(str.replace(/\x1b\]633;[^\x07]+\x07/gs, "").replace(/\x1b\]133;[^\x07]+\x07/gs, "")) + } + + /** + * Helper function to match VSCode shell integration start markers (C). + * Looks for content after ]633;C or ]133;C markers. + * If both exist, takes the content after the last marker found. + */ + private matchAfterVsceStartMarkers(data: string): string | undefined { + return this.matchVsceMarkers(data, "\x1b]633;C", "\x1b]133;C", undefined, undefined) + } + + /** + * Helper function to match VSCode shell integration end markers (D). + * Looks for content before ]633;D or ]133;D markers. + * If both exist, takes the content before the first marker found. + */ + private matchBeforeVsceEndMarkers(data: string): string | undefined { + return this.matchVsceMarkers(data, undefined, undefined, "\x1b]633;D", "\x1b]133;D") + } + + /** + * Handles VSCode shell integration markers for command output: + * + * For C (Command Start): + * - Looks for content after ]633;C or ]133;C markers + * - These markers indicate the start of command output + * - If both exist, takes the content after the last marker found + * - This ensures we get the actual command output after any shell integration prefixes + * + * For D (Command End): + * - Looks for content before ]633;D or ]133;D markers + * - These markers indicate command completion + * - If both exist, takes the content before the first marker found + * - This ensures we don't include shell integration suffixes in the output + * + * In both cases, checks 633 first since it's more commonly used in VSCode shell integration + * + * @param data The string to search for markers in + * @param prefix633 The 633 marker to match after (for C markers) + * @param prefix133 The 133 marker to match after (for C markers) + * @param suffix633 The 633 marker to match before (for D markers) + * @param suffix133 The 133 marker to match before (for D markers) + * @returns The content between/after markers, or undefined if no markers found + * + * Note: Always makes exactly 2 calls to stringIndexMatch regardless of match results. + * Using string indexOf matching is ~500x faster than regular expressions, so even + * matching twice is still very efficient comparatively. + */ + private matchVsceMarkers( + data: string, + prefix633: string | undefined, + prefix133: string | undefined, + suffix633: string | undefined, + suffix133: string | undefined, + ): string | undefined { + // Support both VSCode shell integration markers (633 and 133) + // Check 633 first since it's more commonly used in VSCode shell integration + let match133: string | undefined + const match633 = this.stringIndexMatch(data, prefix633, suffix633) + + // Must check explicitly for undefined because stringIndexMatch can return empty strings + // that are valid matches (e.g., when a marker exists but has no content between markers) + if (match633 !== undefined) { + match133 = this.stringIndexMatch(match633, prefix133, suffix133) + } else { + match133 = this.stringIndexMatch(data, prefix133, suffix133) + } + + return match133 !== undefined ? match133 : match633 } } diff --git a/src/integrations/terminal/TerminalRegistry.ts b/src/integrations/terminal/TerminalRegistry.ts index 2fb49e4825..69a21d94fd 100644 --- a/src/integrations/terminal/TerminalRegistry.ts +++ b/src/integrations/terminal/TerminalRegistry.ts @@ -5,6 +5,9 @@ export interface TerminalInfo { busy: boolean lastCommand: string id: number + stream?: AsyncIterable + running: boolean + streamClosed: boolean } // Although vscode.window.terminals provides a list of all open terminals, there's no way to know whether they're busy or not (exitStatus does not provide useful information for most commands). In order to prevent creating too many terminals, we need to keep track of terminals through the life of the extension, as well as session specific terminals for the life of a task (to get latest unretrieved output). @@ -20,34 +23,61 @@ export class TerminalRegistry { iconPath: new vscode.ThemeIcon("rocket"), env: { PAGER: "cat", + + // VSCode bug#237208: Command output can be lost due to a race between completion + // sequences and consumers. Add 50ms delay via PROMPT_COMMAND to ensure the + // \x1b]633;D escape sequence arrives after command output is processed. + PROMPT_COMMAND: "sleep 0.050", + + // VTE must be disabled because it prevents the prompt command above from executing + // See https://wiki.gnome.org/Apps/Terminal/VTE + VTE_VERSION: "0", }, }) + const newInfo: TerminalInfo = { terminal, busy: false, lastCommand: "", id: this.nextTerminalId++, + running: false, + streamClosed: false, } + this.terminals.push(newInfo) return newInfo } static getTerminal(id: number): TerminalInfo | undefined { const terminalInfo = this.terminals.find((t) => t.id === id) + if (terminalInfo && this.isTerminalClosed(terminalInfo.terminal)) { this.removeTerminal(id) return undefined } + return terminalInfo } static updateTerminal(id: number, updates: Partial) { const terminal = this.getTerminal(id) + if (terminal) { Object.assign(terminal, updates) } } + static getTerminalInfoByTerminal(terminal: vscode.Terminal): TerminalInfo | undefined { + const terminalInfo = this.terminals.find((t) => t.terminal === terminal) + + if (terminalInfo && this.isTerminalClosed(terminalInfo.terminal)) { + this.removeTerminal(terminalInfo.id) + return undefined + } + + return terminalInfo + } + static removeTerminal(id: number) { this.terminals = this.terminals.filter((t) => t.id !== id) } diff --git a/src/integrations/terminal/__tests__/TerminalProcess.test.ts b/src/integrations/terminal/__tests__/TerminalProcess.test.ts index 9ccbaef920..44cae92580 100644 --- a/src/integrations/terminal/__tests__/TerminalProcess.test.ts +++ b/src/integrations/terminal/__tests__/TerminalProcess.test.ts @@ -1,9 +1,24 @@ -import { TerminalProcess, mergePromise } from "../TerminalProcess" -import * as vscode from "vscode" -import { EventEmitter } from "events" +// npx jest src/integrations/terminal/__tests__/TerminalProcess.test.ts -// Mock vscode -jest.mock("vscode") +import * as vscode from "vscode" + +import { TerminalProcess, mergePromise } from "../TerminalProcess" +import { TerminalInfo, TerminalRegistry } from "../TerminalRegistry" + +// Mock vscode.window.createTerminal +const mockCreateTerminal = jest.fn() + +jest.mock("vscode", () => ({ + window: { + createTerminal: (...args: any[]) => { + mockCreateTerminal(...args) + return { + exitStatus: undefined, + } + }, + }, + ThemeIcon: jest.fn(), +})) describe("TerminalProcess", () => { let terminalProcess: TerminalProcess @@ -14,6 +29,7 @@ describe("TerminalProcess", () => { } } > + let mockTerminalInfo: TerminalInfo let mockExecution: any let mockStream: AsyncIterableIterator @@ -25,7 +41,7 @@ describe("TerminalProcess", () => { shellIntegration: { executeCommand: jest.fn(), }, - name: "Mock Terminal", + name: "Roo Code", processId: Promise.resolve(123), creationOptions: {}, exitStatus: undefined, @@ -42,27 +58,39 @@ describe("TerminalProcess", () => { } > + mockTerminalInfo = { + terminal: mockTerminal, + busy: false, + lastCommand: "", + id: 1, + running: false, + streamClosed: false, + } + + TerminalRegistry["terminals"].push(mockTerminalInfo) + // Reset event listeners terminalProcess.removeAllListeners() }) describe("run", () => { it("handles shell integration commands correctly", async () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => { - // Skip empty lines used for loading spinner - if (line !== "") { - lines.push(line) + let lines: string[] = [] + + terminalProcess.on("completed", (output) => { + if (output) { + lines = output.split("\n") } }) - // Mock stream data with shell integration sequences + // Mock stream data with shell integration sequences. mockStream = (async function* () { - // The first chunk contains the command start sequence + yield "\x1b]633;C\x07" // The first chunk contains the command start sequence with bell character. yield "Initial output\n" yield "More output\n" - // The last chunk contains the command end sequence yield "Final output" + yield "\x1b]633;D\x07" // The last chunk contains the command end sequence with bell character. + terminalProcess.emit("shell_execution_complete", mockTerminalInfo.id, { exitCode: 0 }) })() mockExecution = { @@ -71,12 +99,9 @@ describe("TerminalProcess", () => { mockTerminal.shellIntegration.executeCommand.mockReturnValue(mockExecution) - const completedPromise = new Promise((resolve) => { - terminalProcess.once("completed", resolve) - }) - - await terminalProcess.run(mockTerminal, "test command") - await completedPromise + const runPromise = terminalProcess.run(mockTerminal, "test command") + terminalProcess.emit("stream_available", mockTerminalInfo.id, mockStream) + await runPromise expect(lines).toEqual(["Initial output", "More output", "Final output"]) expect(terminalProcess.isHot).toBe(false) @@ -99,95 +124,41 @@ describe("TerminalProcess", () => { }) it("sets hot state for compiling commands", async () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => { - if (line !== "") { - lines.push(line) + let lines: string[] = [] + + terminalProcess.on("completed", (output) => { + if (output) { + lines = output.split("\n") } }) - // Create a promise that resolves when the first chunk is processed - const firstChunkProcessed = new Promise((resolve) => { - terminalProcess.on("line", () => resolve()) + const completePromise = new Promise((resolve) => { + terminalProcess.on("shell_execution_complete", () => resolve()) }) mockStream = (async function* () { + yield "\x1b]633;C\x07" // The first chunk contains the command start sequence with bell character. yield "compiling...\n" - // Wait to ensure hot state check happens after first chunk - await new Promise((resolve) => setTimeout(resolve, 10)) yield "still compiling...\n" yield "done" + yield "\x1b]633;D\x07" // The last chunk contains the command end sequence with bell character. + terminalProcess.emit("shell_execution_complete", mockTerminalInfo.id, { exitCode: 0 }) })() - mockExecution = { + mockTerminal.shellIntegration.executeCommand.mockReturnValue({ read: jest.fn().mockReturnValue(mockStream), - } - - mockTerminal.shellIntegration.executeCommand.mockReturnValue(mockExecution) - - // Start the command execution - const runPromise = terminalProcess.run(mockTerminal, "npm run build") - - // Wait for the first chunk to be processed - await firstChunkProcessed - - // Hot state should be true while compiling - expect(terminalProcess.isHot).toBe(true) - - // Complete the execution - const completedPromise = new Promise((resolve) => { - terminalProcess.once("completed", resolve) }) + const runPromise = terminalProcess.run(mockTerminal, "npm run build") + terminalProcess.emit("stream_available", mockTerminalInfo.id, mockStream) + + expect(terminalProcess.isHot).toBe(true) await runPromise - await completedPromise expect(lines).toEqual(["compiling...", "still compiling...", "done"]) - }) - }) - describe("buffer processing", () => { - it("correctly processes and emits lines", () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => lines.push(line)) - - // Simulate incoming chunks - terminalProcess["emitIfEol"]("first line\n") - terminalProcess["emitIfEol"]("second") - terminalProcess["emitIfEol"](" line\n") - terminalProcess["emitIfEol"]("third line") - - expect(lines).toEqual(["first line", "second line"]) - - // Process remaining buffer - terminalProcess["emitRemainingBufferIfListening"]() - expect(lines).toEqual(["first line", "second line", "third line"]) - }) - - it("handles Windows-style line endings", () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => lines.push(line)) - - terminalProcess["emitIfEol"]("line1\r\nline2\r\n") - - expect(lines).toEqual(["line1", "line2"]) - }) - }) - - describe("removeLastLineArtifacts", () => { - it("removes terminal artifacts from output", () => { - const cases = [ - ["output%", "output"], - ["output$ ", "output"], - ["output#", "output"], - ["output> ", "output"], - ["multi\nline%", "multi\nline"], - ["no artifacts", "no artifacts"], - ] - - for (const [input, expected] of cases) { - expect(terminalProcess["removeLastLineArtifacts"](input)).toBe(expected) - } + await completePromise + expect(terminalProcess.isHot).toBe(false) }) }) @@ -205,13 +176,13 @@ describe("TerminalProcess", () => { describe("getUnretrievedOutput", () => { it("returns and clears unretrieved output", () => { - terminalProcess["fullOutput"] = "previous\nnew output" - terminalProcess["lastRetrievedIndex"] = 9 // After "previous\n" + terminalProcess["fullOutput"] = `\x1b]633;C\x07previous\nnew output\x1b]633;D\x07` + terminalProcess["lastRetrievedIndex"] = 17 // After "previous\n" const unretrieved = terminalProcess.getUnretrievedOutput() - expect(unretrieved).toBe("new output") - expect(terminalProcess["lastRetrievedIndex"]).toBe(terminalProcess["fullOutput"].length) + + expect(terminalProcess["lastRetrievedIndex"]).toBe(terminalProcess["fullOutput"].length - "previous".length) }) }) diff --git a/src/integrations/terminal/__tests__/TerminalRegistry.test.ts b/src/integrations/terminal/__tests__/TerminalRegistry.test.ts index cc667a851b..a2b8fcd3b0 100644 --- a/src/integrations/terminal/__tests__/TerminalRegistry.test.ts +++ b/src/integrations/terminal/__tests__/TerminalRegistry.test.ts @@ -1,4 +1,5 @@ -import * as vscode from "vscode" +// npx jest src/integrations/terminal/__tests__/TerminalRegistry.test.ts + import { TerminalRegistry } from "../TerminalRegistry" // Mock vscode.window.createTerminal @@ -30,6 +31,8 @@ describe("TerminalRegistry", () => { iconPath: expect.any(Object), env: { PAGER: "cat", + PROMPT_COMMAND: "sleep 0.050", + VTE_VERSION: "0", }, }) }) diff --git a/src/shared/api.ts b/src/shared/api.ts index b4dfff2f84..3e96afd01e 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -112,7 +112,7 @@ export const anthropicModels = { thinking: true, }, "claude-3-7-sonnet-20250219": { - maxTokens: 64_000, + maxTokens: 16_384, contextWindow: 200_000, supportsImages: true, supportsComputerUse: true, @@ -437,8 +437,48 @@ export const openRouterDefaultModelInfo: ModelInfo = { export type VertexModelId = keyof typeof vertexModels export const vertexDefaultModelId: VertexModelId = "claude-3-7-sonnet@20250219" export const vertexModels = { + "gemini-2.0-flash-001": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.15, + outputPrice: 0.6, + }, + "gemini-2.0-flash-lite-001": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.075, + outputPrice: 0.3, + }, + "gemini-2.0-flash-thinking-exp-01-21": { + maxTokens: 8192, + contextWindow: 32_768, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + }, + "gemini-1.5-flash-002": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.075, + outputPrice: 0.3, + }, + "gemini-1.5-pro-002": { + maxTokens: 8192, + contextWindow: 2_097_152, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 1.25, + outputPrice: 5, + }, "claude-3-7-sonnet@20250219:thinking": { - maxTokens: 64000, + maxTokens: 64_000, contextWindow: 200_000, supportsImages: true, supportsComputerUse: true, @@ -450,7 +490,7 @@ export const vertexModels = { thinking: true, }, "claude-3-7-sonnet@20250219": { - maxTokens: 8192, + maxTokens: 16_384, contextWindow: 200_000, supportsImages: true, supportsComputerUse: true, diff --git a/src/shared/combineCommandSequences.ts b/src/shared/combineCommandSequences.ts index 31fe219f04..cbd674fc07 100644 --- a/src/shared/combineCommandSequences.ts +++ b/src/shared/combineCommandSequences.ts @@ -44,7 +44,7 @@ export function combineCommandSequences(messages: ClineMessage[]): ClineMessage[ // handle cases where we receive empty command_output (ie when extension is relinquishing control over exit command button) const output = messages[j].text || "" if (output.length > 0) { - combinedText += "\n" + output + combinedText += output } } j++ diff --git a/src/shared/modes.ts b/src/shared/modes.ts index ceb1e3e11f..33257a7013 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -36,7 +36,11 @@ export type CustomModePrompts = { // Helper to extract group name regardless of format export function getGroupName(group: GroupEntry): ToolGroup { - return Array.isArray(group) ? group[0] : group + if (typeof group === "string") { + return group + } + + return group[0] } // Helper to get group options if they exist @@ -88,7 +92,7 @@ export const modes: readonly ModeConfig[] = [ "You are Roo, an experienced technical leader who is inquisitive and an excellent planner. Your goal is to gather information and get context to create a detailed plan for accomplishing the user's task, which the user will review and approve before they switch into another mode to implement the solution.", groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], customInstructions: - "Depending on the user's request, you may need to do some information gathering (for example using read_file or search_files) to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. (You can write the plan to a markdown file if it seems appropriate.)\n\nThen you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it. Finally once it seems like you've reached a good plan, use the switch_mode tool to request that the user switch to another mode to implement the solution.", + "1. Do some information gathering (for example using read_file or search_files) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer.\n\n4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.\n\n5. Once the user confirms the plan, ask them if they'd like you to write it to a markdown file.\n\n6. Use the switch_mode tool to request that the user switch to another mode to implement the solution.", }, { slug: "ask", @@ -97,7 +101,7 @@ export const modes: readonly ModeConfig[] = [ "You are Roo, a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics.", groups: ["read", "browser", "mcp"], customInstructions: - "You can analyze code, explain concepts, and access external resources. Make sure to answer the user's questions and don't rush to switch to implementing code.", + "You can analyze code, explain concepts, and access external resources. Make sure to answer the user's questions and don't rush to switch to implementing code. Include Mermaid diagrams if they help make your response clearer.", }, { slug: "debug", diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index 22564d01a6..b614ba387e 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -28,6 +28,7 @@ "fast-deep-equal": "^3.1.3", "fzf": "^0.5.2", "lucide-react": "^0.475.0", + "mermaid": "^11.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.3", @@ -100,6 +101,26 @@ "node": ">=6.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz", + "integrity": "sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==", + "dependencies": { + "package-manager-detector": "^0.2.8", + "tinyexec": "^0.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", + "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -2196,6 +2217,45 @@ "dev": true, "license": "MIT" }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==" + }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", @@ -2785,6 +2845,37 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, + "node_modules/@iconify/utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "dependencies": { + "@antfu/install-pkg": "^1.0.0", + "@antfu/utils": "^8.1.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.0", + "globals": "^15.14.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "mlly": "^1.7.4" + } + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3529,6 +3620,14 @@ "react": ">=16" } }, + "node_modules/@mermaid-js/parser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", + "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==", + "dependencies": { + "langium": "3.0.0" + } + }, "node_modules/@microsoft/fast-element": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.14.0.tgz", @@ -6484,6 +6583,228 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -6515,6 +6836,11 @@ "@types/estree": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -6726,6 +7052,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -7358,7 +7690,6 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -8312,6 +8643,30 @@ "node": ">= 16" } }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -8864,6 +9219,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8871,6 +9234,11 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" + }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -8908,6 +9276,14 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -9054,6 +9430,463 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.31.1.tgz", + "integrity": "sha512-Hx5Mtb1+hnmAKaZZ/7zL1Y5HTFYOjdDswZy/jD+1WINRU8KVi1B7+vlHdsTwY+VCFucTreoyu1RDzQJ9u0d2Hw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -9130,6 +9963,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "node_modules/debounce": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", @@ -9279,6 +10117,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9397,6 +10243,14 @@ "node": ">=12" } }, + "node_modules/dompurify": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -11586,6 +12440,11 @@ "dev": true, "license": "MIT" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==" + }, "node_modules/harmony-reflect": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", @@ -12041,7 +12900,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -12179,6 +13037,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -14058,6 +14924,29 @@ "node": ">=4.0" } }, + "node_modules/katex": { + "version": "0.16.21", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", + "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -14068,6 +14957,11 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -14078,6 +14972,26 @@ "node": ">=6" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==" + }, + "node_modules/langium": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz", + "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -14098,6 +15012,11 @@ "node": ">=0.10" } }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -14357,6 +15276,22 @@ "dev": true, "license": "MIT" }, + "node_modules/local-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.0.tgz", + "integrity": "sha512-xbZBuX6gYIWrlLmZG43aAVer4ocntYO09vPy9lxd6Ns8DnR4U7N+IIeDkubinqFOHHzoMlPxTxwo0jhE7oYjAw==", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^1.3.1", + "quansync": "^0.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -14380,6 +15315,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -14550,6 +15490,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", + "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -15869,6 +16820,33 @@ "node": ">= 8" } }, + "node_modules/mermaid": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.1.tgz", + "integrity": "sha512-Mb01JT/x6CKDWaxigwfZYuYmDZ6xtrNwNlidKZwkSrDaY9n90tdrJTV5Umk+wP1fZscGptmKFXHsXMDEVZ+Q6A==", + "dependencies": { + "@braintree/sanitize-url": "^7.0.1", + "@iconify/utils": "^2.1.32", + "@mermaid-js/parser": "^0.3.0", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.2", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.10", + "dompurify": "^3.2.1", + "katex": "^0.16.9", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^13.0.2", + "roughjs": "^4.6.6", + "stylis": "^4.3.1", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.1" + } + }, "node_modules/micromark": { "version": "2.11.4", "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", @@ -16507,6 +17485,17 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -16872,6 +17861,14 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.10.tgz", + "integrity": "sha512-1wlNZK7HW+UE3eGCcMv3hDaYokhspuIeH6enXSnCL1eEZSVDsy/dYwo/4CczhUsrKLA1SSXB+qce8Glw5DEVtw==", + "dependencies": { + "quansync": "^0.2.2" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -16935,6 +17932,11 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -17006,6 +18008,11 @@ "node": ">=8" } }, + "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/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", @@ -17116,6 +18123,30 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/polished": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", @@ -17307,6 +18338,21 @@ ], "license": "MIT" }, + "node_modules/quansync": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.6.tgz", + "integrity": "sha512-u3TuxVTuJtkTxKGk5oZ7K2/o+l0/cC6J8SOyaaSnrnroqvcVy7xBxtvBUyd+Xa8cGoCr87XmQj4NR6W+zbqH8w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ] + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -18434,6 +19480,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rollup": { "version": "4.32.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz", @@ -18472,6 +19523,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/rtl-css-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", @@ -18505,6 +19567,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -18564,7 +19631,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/saxes": { @@ -19441,6 +20507,11 @@ "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==" + }, "node_modules/tinyrainbow": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", @@ -19557,7 +20628,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.10" @@ -19813,6 +20883,11 @@ "node": ">=4.2.0" } }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==" + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -20260,7 +21335,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -20397,6 +21471,49 @@ } } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" + }, "node_modules/vscrui": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/vscrui/-/vscrui-0.2.2.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 2206fb35c9..12786d1060 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -35,6 +35,7 @@ "fast-deep-equal": "^3.1.3", "fzf": "^0.5.2", "lucide-react": "^0.475.0", + "mermaid": "^11.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.3", diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index dcbe085147..a2af6a3a71 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -31,6 +31,7 @@ interface ChatTextAreaProps { onHeightChange?: (height: number) => void mode: Mode setMode: (value: Mode) => void + modeShortcutText: string } const ChatTextArea = forwardRef( @@ -48,6 +49,7 @@ const ChatTextArea = forwardRef( onHeightChange, mode, setMode, + modeShortcutText, }, ref, ) => { @@ -816,6 +818,11 @@ const ChatTextArea = forwardRef( minWidth: "70px", flex: "0 0 auto", }}> + {getAllModes(customModes).map((mode) => (