mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-05 08:51:16 +00:00
Compare commits
14 Commits
v0.48.0
...
sd-can-use
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
025038e8b5 | ||
|
|
9616bdac47 | ||
|
|
1fe7ec09a3 | ||
|
|
228f02ecfb | ||
|
|
23626c9a43 | ||
|
|
8f6ebd1d91 | ||
|
|
07911601cc | ||
|
|
a7e6137f68 | ||
|
|
044c43e0c8 | ||
|
|
9013d1d468 | ||
|
|
d5e1b7fa72 | ||
|
|
960f62d765 | ||
|
|
508453c288 | ||
|
|
8dc8c86426 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
with:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.48.0"
|
||||
".": "0.51.0"
|
||||
}
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## [0.51.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.50.0...v0.51.0) (2026-06-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** bump the minor group with 11 updates ([#807](https://github.com/agentclientprotocol/claude-agent-acp/issues/807)) ([8f6ebd1](https://github.com/agentclientprotocol/claude-agent-acp/commit/8f6ebd1d9198edf723f4c8c1aa2b49b906c46646))
|
||||
|
||||
## [0.50.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.49.0...v0.50.0) (2026-06-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **acp:** Handle ACP request cancellation signals ([#801](https://github.com/agentclientprotocol/claude-agent-acp/issues/801)) ([9013d1d](https://github.com/agentclientprotocol/claude-agent-acp/commit/9013d1d46883a7f3774a63a9dca16c4a0f634a97))
|
||||
* **deps:** bump actions/checkout from 6.0.3 to 7.0.0 ([#803](https://github.com/agentclientprotocol/claude-agent-acp/issues/803)) ([044c43e](https://github.com/agentclientprotocol/claude-agent-acp/commit/044c43e0c894082b9e747c01e9dbde6e21036823))
|
||||
* **deps:** upgrade to @anthropic-ai/claude-agent-sdk@0.3.186 ([#806](https://github.com/agentclientprotocol/claude-agent-acp/issues/806)) ([a7e6137](https://github.com/agentclientprotocol/claude-agent-acp/commit/a7e6137f6877b72b8daa39e74971c3559db8d28f))
|
||||
|
||||
## [0.49.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.48.0...v0.49.0) (2026-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to claude-agent-sdk 0.3.185 ([#798](https://github.com/agentclientprotocol/claude-agent-acp/issues/798)) ([8dc8c86](https://github.com/agentclientprotocol/claude-agent-acp/commit/8dc8c864263fa50d03bec7ebff7aee776604cb3d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Deduplicate streamed assistant blocks by content ([#800](https://github.com/agentclientprotocol/claude-agent-acp/issues/800)) ([960f62d](https://github.com/agentclientprotocol/claude-agent-acp/commit/960f62d76582ae5c9c5575ab66974809049ce1d0))
|
||||
* Infer 1M context from model descriptions ([#799](https://github.com/agentclientprotocol/claude-agent-acp/issues/799)) ([508453c](https://github.com/agentclientprotocol/claude-agent-acp/commit/508453c288b4a12701abd507199e7fa0ab172171))
|
||||
|
||||
## [0.48.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.47.0...v0.48.0) (2026-06-19)
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export default [
|
||||
"default-case": "error",
|
||||
"prefer-const": "error",
|
||||
"no-var": "error",
|
||||
eqeqeq: ["error", "always"],
|
||||
eqeqeq: ["error", "smart"],
|
||||
curly: ["error", "all"],
|
||||
},
|
||||
},
|
||||
|
||||
442
package-lock.json
generated
442
package-lock.json
generated
@@ -1,62 +1,66 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.48.0",
|
||||
"version": "0.51.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.48.0",
|
||||
"version": "0.51.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.28.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.183",
|
||||
"@agentclientprotocol/sdk": "1.0.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.191",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.105.0",
|
||||
"@anthropic-ai/sdk": "0.106.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"@tsconfig/node22": "22.0.5",
|
||||
"@types/node": "26.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.62.0",
|
||||
"@typescript-eslint/parser": "8.62.0",
|
||||
"eslint": "10.5.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.6.0",
|
||||
"globals": "17.7.0",
|
||||
"prettier": "3.8.4",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.28.1.tgz",
|
||||
"integrity": "sha512-Z2Frs6YtPhnZZ+XwFXyQkRDXY0fn8FjCalEs0W4yUhQnY4TztmNq0/RnfzWdFN3vqT3h0jTz5klzYbZHGxCDyQ==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.0.0.tgz",
|
||||
"integrity": "sha512-5FHgLbLZhSAq9F+9SU7AOCXTDzaw/IYx/AvMjrZDs/YFZ9ZPxkdn9ti3fSkny0huHfj2hrqF6pqdns9gN5/Fig==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.183.tgz",
|
||||
"integrity": "sha512-rAKws76RKdFu+DK5q9wYN0AGLRdPrw0vGd+TMzSFynFdxwUAehYc9vUKJoK27j2xRoHFdNt+JDsih5BzsUzi+Q==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.191.tgz",
|
||||
"integrity": "sha512-DM9oYbt+WAb/CiD8l/CO6akqPKfyZ4sL1e+lP2O2eS9IR0f58sf90xWeji7V+/zfnIp+jyH3hJ3V6r7odCdB8g==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.183",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.183",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.183",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.183",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.183",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.183",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.183",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.183"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.191",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.191",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.191",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.191",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.191",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.191",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.191",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.191"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -65,9 +69,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.183.tgz",
|
||||
"integrity": "sha512-o/+sxwKgXuw6RG5cERWjvcvL1CDSPe/TaXMhax+dq+V4lDOI5iTqg3y5Wfb6dL3xlWoTA2OhWowDQllKbE04LQ==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.191.tgz",
|
||||
"integrity": "sha512-rmpi5ONfuuw7oXdzyr9QJB5sK9Rr1MmdOUsiGutVFpcAqqSXpVYLmMvfaarmXvItgfwgGprzrGDfcAeAQHSUGw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -78,9 +82,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.183.tgz",
|
||||
"integrity": "sha512-V7Cf8JeD5EPf4MPomFUlEblCIQI0wg+aWdOSqvfMsDmCBEHljd52CQ3a7W263oVt6I7QUfRTpX2KNvdma56rDA==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.191.tgz",
|
||||
"integrity": "sha512-b/4pfSFTC2gkc0RoGSJ5B3DWo77qZFMi2hRlx8kwaRDiefeIOnbJu42gDBrs4F8nSJJug77HM9AsG6XHXvt5Fg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -91,9 +95,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.183.tgz",
|
||||
"integrity": "sha512-qqnSf85D3uBH7kRHLGOMhr7aQrREUZ4hUlCztrkJe/twUT4bmJsO86zamnAf7vOYeCul/sHYwBMmXtOfI+cHZg==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.191.tgz",
|
||||
"integrity": "sha512-vm526e3InpItmh+Ua8a49eGTpFJvZTYLURX9ppKrtFb+Wo7rOgoXmK/Hzf/crEU7h72xtaD7PIOXm4P+psxO3w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -107,9 +111,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.183.tgz",
|
||||
"integrity": "sha512-ua6XaiCcYYKfd0CY9cjoyYnTQWI2GocpbP7Gwvmr+9taW/lTpQGVQH3FrF2VFKXVSt7AaVE6Z7WN9oPxcxl3VA==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.191.tgz",
|
||||
"integrity": "sha512-+Gg7lKwYDr8uiA37EBMYI63if+YGdbrFt2tcRFBB4IhdVf5r1IkKwQ8rEYV6Y0nqGTKoXLh3bRc5QWOe5JwgbQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -123,9 +127,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.183.tgz",
|
||||
"integrity": "sha512-GvfNp9XsKWFvr0EH3NgX2pqzHPYhFeqhOmVc4A+2e+GdxBMYUEa0ylfPLJItahQU9FkTJAw2oQNnzTHPPm1GxA==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.191.tgz",
|
||||
"integrity": "sha512-wj43/l0Behb4UWYt6Lomq2W7jhmuU7ARsEP0el6gLGCR348PDeqLGcB4dMOcYMGRZv8TYKfnhDwJnX4pJBT1vQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -139,9 +143,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.183.tgz",
|
||||
"integrity": "sha512-wp+p+4xeZOW+h4InMKR7nIdGlgYftXyR+ErOVz7i2tMRcKuYMoc5jRIYjdJkuETMSr5nsRj0rDeO5gG7M6MGZQ==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.191.tgz",
|
||||
"integrity": "sha512-tUwwIXpPBrzJge3E4SQJQX8PuC+lxPb5xQBmyLsWxyjttlgpFpTG/jWPVQO5qH0HgYaEJS5JALHFiYcXwVrEkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -155,9 +159,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.183.tgz",
|
||||
"integrity": "sha512-cGyMC9YDsrCQ4av74dMVa2liOb5oLFX4+1SxwJGRCW7mBVBazcM6rg7ifWbd9XfkDhNPKOIfyiloipyMfoaE4g==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.191.tgz",
|
||||
"integrity": "sha512-J8aTOi7n6AM7tHFlKt6hlJ4rue0TbY2glqcI2r6K3iK7Fmzvt9zfg00+l8fWKOais4K+8jqJCC4WSCUlz6+tKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -168,9 +172,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.183",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.183.tgz",
|
||||
"integrity": "sha512-h/XzbrSmXGroTk/FYKR6J4/8G9vDb1HUUUeNXeBGqGW1kppIiWPJKLRzjtSe0brVjADOKOT6tE5IHK0mV/1gBw==",
|
||||
"version": "0.3.191",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.191.tgz",
|
||||
"integrity": "sha512-1rYlPuM9PS37IPCE/sAG7A7oCrWnIb0J7EY6eQBdImdXHEjwBngZ3E8KbtEM79fUTA3C810LqrnFpjOmNExLMA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -181,9 +185,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.105.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.105.0.tgz",
|
||||
"integrity": "sha512-sDyu+aM9cE6uZE+HgRjjHRb+qqb87GHZOx+8bE0YlWetdL1YcVLxn8h9ltxGOflyChTe6PMEo50kMQV4cw0hfg==",
|
||||
"version": "0.106.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.106.0.tgz",
|
||||
"integrity": "sha512-ufwVvYNDBj2dzOGupBCTaNzBLxqcTnGOzI4z8Wouxlt+mT3J3HuOmatgCy1VmwCHOUueqZ41ERhm0O99OUcbWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
@@ -224,21 +228,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
|
||||
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -247,9 +251,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -521,14 +525,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
|
||||
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
|
||||
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.2"
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -540,9 +544,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.133.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
|
||||
"version": "0.137.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz",
|
||||
"integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -550,9 +554,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz",
|
||||
"integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -567,9 +571,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz",
|
||||
"integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -584,9 +588,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz",
|
||||
"integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -601,9 +605,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz",
|
||||
"integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -618,9 +622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz",
|
||||
"integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -635,9 +639,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -655,9 +659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz",
|
||||
"integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -675,9 +679,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -695,9 +699,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -715,9 +719,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -735,9 +739,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz",
|
||||
"integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -755,9 +759,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz",
|
||||
"integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -772,9 +776,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
|
||||
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz",
|
||||
"integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -782,18 +786,18 @@
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.10.0",
|
||||
"@emnapi/runtime": "1.10.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.4"
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1",
|
||||
"@napi-rs/wasm-runtime": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz",
|
||||
"integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -808,9 +812,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz",
|
||||
"integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -872,10 +876,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node22": {
|
||||
"version": "22.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.5.tgz",
|
||||
"integrity": "sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
||||
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -923,9 +934,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
|
||||
"integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
|
||||
"version": "26.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
|
||||
"integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -933,17 +944,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz",
|
||||
"integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz",
|
||||
"integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.61.1",
|
||||
"@typescript-eslint/type-utils": "8.61.1",
|
||||
"@typescript-eslint/utils": "8.61.1",
|
||||
"@typescript-eslint/visitor-keys": "8.61.1",
|
||||
"@typescript-eslint/scope-manager": "8.62.0",
|
||||
"@typescript-eslint/type-utils": "8.62.0",
|
||||
"@typescript-eslint/utils": "8.62.0",
|
||||
"@typescript-eslint/visitor-keys": "8.62.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -956,22 +967,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.61.1",
|
||||
"@typescript-eslint/parser": "^8.62.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz",
|
||||
"integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz",
|
||||
"integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.61.1",
|
||||
"@typescript-eslint/types": "8.61.1",
|
||||
"@typescript-eslint/typescript-estree": "8.61.1",
|
||||
"@typescript-eslint/visitor-keys": "8.61.1",
|
||||
"@typescript-eslint/scope-manager": "8.62.0",
|
||||
"@typescript-eslint/types": "8.62.0",
|
||||
"@typescript-eslint/typescript-estree": "8.62.0",
|
||||
"@typescript-eslint/visitor-keys": "8.62.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -987,14 +998,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz",
|
||||
"integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz",
|
||||
"integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.61.1",
|
||||
"@typescript-eslint/types": "^8.61.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.62.0",
|
||||
"@typescript-eslint/types": "^8.62.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1009,14 +1020,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz",
|
||||
"integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz",
|
||||
"integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.61.1",
|
||||
"@typescript-eslint/visitor-keys": "8.61.1"
|
||||
"@typescript-eslint/types": "8.62.0",
|
||||
"@typescript-eslint/visitor-keys": "8.62.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1027,9 +1038,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz",
|
||||
"integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz",
|
||||
"integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1044,15 +1055,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz",
|
||||
"integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz",
|
||||
"integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.61.1",
|
||||
"@typescript-eslint/typescript-estree": "8.61.1",
|
||||
"@typescript-eslint/utils": "8.61.1",
|
||||
"@typescript-eslint/types": "8.62.0",
|
||||
"@typescript-eslint/typescript-estree": "8.62.0",
|
||||
"@typescript-eslint/utils": "8.62.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -1069,9 +1080,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz",
|
||||
"integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz",
|
||||
"integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1083,16 +1094,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz",
|
||||
"integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz",
|
||||
"integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.61.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.61.1",
|
||||
"@typescript-eslint/types": "8.61.1",
|
||||
"@typescript-eslint/visitor-keys": "8.61.1",
|
||||
"@typescript-eslint/project-service": "8.62.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.62.0",
|
||||
"@typescript-eslint/types": "8.62.0",
|
||||
"@typescript-eslint/visitor-keys": "8.62.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1111,16 +1122,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz",
|
||||
"integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz",
|
||||
"integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.61.1",
|
||||
"@typescript-eslint/types": "8.61.1",
|
||||
"@typescript-eslint/typescript-estree": "8.61.1"
|
||||
"@typescript-eslint/scope-manager": "8.62.0",
|
||||
"@typescript-eslint/types": "8.62.0",
|
||||
"@typescript-eslint/typescript-estree": "8.62.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1135,13 +1146,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz",
|
||||
"integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==",
|
||||
"version": "8.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz",
|
||||
"integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.61.1",
|
||||
"@typescript-eslint/types": "8.62.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2308,9 +2319,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.6.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz",
|
||||
"integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==",
|
||||
"version": "17.7.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz",
|
||||
"integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2360,9 +2371,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.26",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz",
|
||||
"integrity": "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==",
|
||||
"version": "4.12.27",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz",
|
||||
"integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -2954,9 +2965,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.13",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz",
|
||||
"integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==",
|
||||
"version": "3.3.15",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
|
||||
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3256,13 +3267,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
|
||||
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
"es-define-property": "^1.0.1",
|
||||
"side-channel": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@@ -3308,13 +3320,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
|
||||
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz",
|
||||
"integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.133.0",
|
||||
"@oxc-project/types": "=0.137.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3324,21 +3336,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-x64": "1.0.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.3"
|
||||
"@rolldown/binding-android-arm64": "1.1.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.3",
|
||||
"@rolldown/binding-darwin-x64": "1.1.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
@@ -3366,9 +3378,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
|
||||
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -3810,16 +3822,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
|
||||
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz",
|
||||
"integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.15",
|
||||
"rolldown": "1.0.3",
|
||||
"rolldown": "~1.1.2",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3836,7 +3848,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"@vitejs/devtools": "^0.1.18",
|
||||
"@vitejs/devtools": "^0.3.0",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
|
||||
20
package.json
20
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.48.0",
|
||||
"version": "0.51.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -59,20 +59,24 @@
|
||||
},
|
||||
"author": "Zed Industries",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.28.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.183",
|
||||
"@agentclientprotocol/sdk": "1.0.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.191",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.105.0",
|
||||
"@anthropic-ai/sdk": "0.106.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@types/node": "26.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.61.1",
|
||||
"@typescript-eslint/parser": "8.61.1",
|
||||
"@tsconfig/node22": "22.0.5",
|
||||
"@types/node": "26.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.62.0",
|
||||
"@typescript-eslint/parser": "8.62.0",
|
||||
"eslint": "10.5.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.6.0",
|
||||
"globals": "17.7.0",
|
||||
"prettier": "3.8.4",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
|
||||
458
src/acp-agent.ts
458
src/acp-agent.ts
@@ -50,6 +50,7 @@ import {
|
||||
AgentInfo,
|
||||
CanUseTool,
|
||||
deleteSession,
|
||||
getSessionInfo,
|
||||
getSessionMessages,
|
||||
listSessions,
|
||||
McpServerConfig,
|
||||
@@ -262,12 +263,23 @@ type Session = {
|
||||
/** Accumulated task list for the session, keyed by task ID. Task IDs are
|
||||
* per-session, so this state must not be shared across sessions. */
|
||||
taskState: TaskState;
|
||||
/** Last session title we pushed to the client via `session_info_update`.
|
||||
* The SDK auto-generates a title in a background task and persists it to the
|
||||
* session file; we poll it on each turn-end (`session_state_changed: idle`)
|
||||
* and only notify the client when it actually changes. Undefined until the
|
||||
* first title is observed. */
|
||||
lastTitle?: string;
|
||||
/** Caches `tool_use` blocks by id so the matching `tool_result` can recover
|
||||
* the tool name/input when mapping it to a `tool_call_update`. Per-session
|
||||
* (tool_use ids are only unique within a session) and pruned at
|
||||
* `tool_result` time so a long-running session doesn't accumulate every
|
||||
* tool call for its whole lifetime. */
|
||||
toolUseCache: ToolUseCache;
|
||||
/**
|
||||
* This lets canUseTool wait for the first tool call message from the SDK
|
||||
* to ensure it emits a tool_call_update only after the first tool_call message.
|
||||
*/
|
||||
waitingForToolCall: WaitingForToolCall;
|
||||
/** Maps the ACP `messageId` we expose to clients (see `messageIdForGrouping`)
|
||||
* to the SDK message uuid that the Agent SDK's rewind/resume APIs key on
|
||||
* (`Query.rewindFiles` takes a user-message uuid; `resumeSessionAt` takes an
|
||||
@@ -382,6 +394,8 @@ export type ToolUpdateMeta = {
|
||||
};
|
||||
};
|
||||
|
||||
export type WaitingForToolCall = Map<string, () => void>;
|
||||
|
||||
export type ToolUseCache = {
|
||||
[key: string]: {
|
||||
type: "tool_use" | "server_tool_use" | "mcp_tool_use";
|
||||
@@ -635,10 +649,21 @@ export function describeAlwaysAllow(
|
||||
*/
|
||||
export interface AcpClient {
|
||||
sessionUpdate(params: SessionNotification): Promise<void>;
|
||||
requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse>;
|
||||
/** `signal`, when aborted, sends `$/cancel_request` for the in-flight
|
||||
* permission request so the client can dismiss its prompt (and settle our
|
||||
* await) instead of leaving the dialog open after the turn was cancelled. */
|
||||
requestPermission(
|
||||
params: RequestPermissionRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<RequestPermissionResponse>;
|
||||
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
|
||||
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
|
||||
unstable_createElicitation(params: CreateElicitationRequest): Promise<CreateElicitationResponse>;
|
||||
/** `signal`, when aborted, sends `$/cancel_request` for the in-flight
|
||||
* elicitation so the client can dismiss its prompt and settle our await. */
|
||||
unstable_createElicitation(
|
||||
params: CreateElicitationRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CreateElicitationResponse>;
|
||||
unstable_completeElicitation(params: CompleteElicitationNotification): Promise<void>;
|
||||
/** Send a custom (extension) notification, e.g. `_claude/sdkMessage`. */
|
||||
extNotification(method: string, params: Record<string, unknown>): Promise<void>;
|
||||
@@ -657,8 +682,13 @@ class ClientConnection implements AcpClient {
|
||||
return this.ctx.notify(methods.client.session.update, params);
|
||||
}
|
||||
|
||||
requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
|
||||
return this.ctx.request(methods.client.session.requestPermission, params);
|
||||
requestPermission(
|
||||
params: RequestPermissionRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<RequestPermissionResponse> {
|
||||
return this.ctx.request(methods.client.session.requestPermission, params, {
|
||||
cancellationSignal: signal,
|
||||
});
|
||||
}
|
||||
|
||||
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
|
||||
@@ -669,8 +699,13 @@ class ClientConnection implements AcpClient {
|
||||
return this.ctx.request(methods.client.fs.writeTextFile, params);
|
||||
}
|
||||
|
||||
unstable_createElicitation(params: CreateElicitationRequest): Promise<CreateElicitationResponse> {
|
||||
return this.ctx.request(methods.client.elicitation.create, params);
|
||||
unstable_createElicitation(
|
||||
params: CreateElicitationRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CreateElicitationResponse> {
|
||||
return this.ctx.request(methods.client.elicitation.create, params, {
|
||||
cancellationSignal: signal,
|
||||
});
|
||||
}
|
||||
|
||||
unstable_completeElicitation(params: CompleteElicitationNotification): Promise<void> {
|
||||
@@ -922,6 +957,40 @@ export class ClaudeAcpAgent {
|
||||
};
|
||||
}
|
||||
|
||||
/** Read the SDK-maintained title for a session and, if it changed since the
|
||||
* last time we looked, notify the client with a `session_info_update`. The
|
||||
* SDK has no push event for the title it auto-generates in the background, so
|
||||
* we pull it at turn-end. A missing session file or read error is non-fatal:
|
||||
* the title is best-effort and another turn will retry. */
|
||||
private async maybeUpdateSessionTitle(sessionId: string, session: Session): Promise<void> {
|
||||
let info;
|
||||
try {
|
||||
info = await getSessionInfo(sessionId, { dir: session.cwd });
|
||||
} catch (error) {
|
||||
this.logger.error(`Session ${sessionId}: failed to read session info: ${error}`);
|
||||
return;
|
||||
}
|
||||
// `customTitle` is a user-set `/rename`; `summary` is the auto-generated
|
||||
// title (or first prompt). Prefer the explicit title when present.
|
||||
const rawTitle = info?.customTitle ?? info?.summary;
|
||||
if (!rawTitle) {
|
||||
return;
|
||||
}
|
||||
const title = sanitizeTitle(rawTitle);
|
||||
if (title === session.lastTitle) {
|
||||
return;
|
||||
}
|
||||
session.lastTitle = title;
|
||||
await this.client.sessionUpdate({
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "session_info_update",
|
||||
title,
|
||||
updatedAt: new Date(info!.lastModified).toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async authenticate(_params: AuthenticateRequest): Promise<void> {
|
||||
if (_params.methodId === "gateway" || _params.methodId === "gateway-bedrock") {
|
||||
this.gatewayAuthRequest = _params as GatewayAuthRequest;
|
||||
@@ -1024,12 +1093,17 @@ export class ClaudeAcpAgent {
|
||||
// (whose delta events don't carry it) can all be tagged with the same,
|
||||
// replay-stable id.
|
||||
let currentStreamMessageId: string | undefined;
|
||||
// Per-message-id record of which assistant content actually streamed live
|
||||
// via `stream_event` deltas, split by block type, so the consolidated
|
||||
// `assistant` message can drop the duplicate blocks that already reached the
|
||||
// client as chunks while still forwarding any that a non-streaming gateway
|
||||
const streamedTextIds = new Set<string>();
|
||||
const streamedThinkingIds = new Set<string>();
|
||||
// The text/thinking blocks that have actually streamed live as
|
||||
// `stream_event` deltas for the message the next consolidated `assistant`
|
||||
// will repeat, in stream order, each accumulated to its full streamed text.
|
||||
// The consolidated handler diffs each assembled block against these and
|
||||
// forwards only the un-streamed remainder — nothing if it streamed in full
|
||||
// (the common case), the whole block if it never streamed (a non-streaming
|
||||
// gateway), or just the tail if the stream was cut short mid-block. Matching
|
||||
// on content rather than the Anthropic message id makes dedupe robust to
|
||||
// gateways that don't carry a stable/matching id across the stream and the
|
||||
// consolidated message. Reset after each consolidated message consumes it.
|
||||
const streamedBlocks: { index: number; type: "text" | "thinking"; text: string }[] = [];
|
||||
// Stop reason accumulated for the active turn (result subtype, refusal,
|
||||
// max_tokens, …). Reset per turn; read when the turn settles at idle.
|
||||
let stopReason: StopReason = "end_turn";
|
||||
@@ -1041,12 +1115,14 @@ export class ClaudeAcpAgent {
|
||||
lastAssistantError = undefined;
|
||||
lastRefusalExplanation = null;
|
||||
compactionInProgress = false;
|
||||
// Do NOT reset currentStreamMessageId here. `message_start` sets it per
|
||||
// message; clearing it on turn activation drops the id for any block that
|
||||
// streams after a mid-message activation (the replayed user echo with
|
||||
// --replay-user-messages), so those ids never enter streamedTextIds and the
|
||||
// consolidated assistant message re-emits already-streamed text as a
|
||||
// duplicate. #785 stopped resetting streamedTextIds here but left this line.
|
||||
// Do NOT reset currentStreamMessageId or streamedBlocks here. Turn
|
||||
// activation can fire mid-message (the replayed user echo with
|
||||
// --replay-user-messages lands between a message's blocks); clearing the
|
||||
// streamed-content record on activation would drop the blocks that
|
||||
// streamed before the echo, so the consolidated assistant message would
|
||||
// re-emit them as duplicates. streamedBlocks is bounded instead by being
|
||||
// cleared when each consolidated message consumes it. #785 stopped
|
||||
// resetting the streamed-content tracking here but left this line.
|
||||
stopReason = "end_turn";
|
||||
session.accumulatedUsage = {
|
||||
inputTokens: 0,
|
||||
@@ -1337,6 +1413,11 @@ export class ClaudeAcpAgent {
|
||||
if (session.cancelled) {
|
||||
settleActive({ stopReason: "cancelled" });
|
||||
}
|
||||
// The SDK generates the session title in a background task and
|
||||
// persists it to the session file; `idle` is the turn-over
|
||||
// signal, so it's the point at which a new title may have
|
||||
// landed. Push it to the client if it changed.
|
||||
await this.maybeUpdateSessionTitle(params.sessionId, session);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1615,6 +1696,7 @@ export class ClaudeAcpAgent {
|
||||
"assistant",
|
||||
params.sessionId,
|
||||
session.toolUseCache,
|
||||
session.waitingForToolCall,
|
||||
this.client,
|
||||
this.logger,
|
||||
)) {
|
||||
@@ -1682,21 +1764,52 @@ export class ClaudeAcpAgent {
|
||||
// it) can all be tagged with the same, replay-stable id.
|
||||
if (message.event.type === "message_start") {
|
||||
currentStreamMessageId = message.event.message.id || undefined;
|
||||
// A new top-level message starts: clear any streamed-content
|
||||
// residue from a prior message that never reached its
|
||||
// consolidated reset — a cancelled turn breaks out before the
|
||||
// reset, and the synthetic-auth/system/local-command paths
|
||||
// `break` early too. Block indices restart at 0 each message, so
|
||||
// leftover entries would otherwise collide with this message's
|
||||
// blocks and re-emit (or truncate) already-streamed text. Gated on
|
||||
// `parent_tool_use_id === null` so a subagent stream can't clear
|
||||
// the top-level record. Fires once, before any of this message's
|
||||
// blocks, so it doesn't disturb the mid-message turn-activation
|
||||
// path the way resetting on turn activation would.
|
||||
if (message.parent_tool_use_id === null) {
|
||||
streamedBlocks.length = 0;
|
||||
}
|
||||
}
|
||||
// Record that this top-level message id actually streamed text/
|
||||
// thinking, so the `assistant` case below knows its assembled blocks
|
||||
// are duplicates (filter them) rather than the only copy (forward
|
||||
// them). Gated on `parent_tool_use_id === null` so a subagent stream
|
||||
// can't attribute its content to the top-level message id.
|
||||
// Accumulate the text/thinking actually streamed live, so the
|
||||
// `assistant` case below can diff its assembled blocks against what
|
||||
// already reached the client as chunks and forward only the
|
||||
// remainder. Gated on `parent_tool_use_id === null` so a subagent
|
||||
// stream can't attribute its content to the top-level message.
|
||||
// Contiguous deltas of the same block (same index and type) extend
|
||||
// the current entry; anything else opens a new one.
|
||||
if (
|
||||
currentStreamMessageId &&
|
||||
message.parent_tool_use_id === null &&
|
||||
message.event.type === "content_block_delta"
|
||||
) {
|
||||
if (message.event.delta.type === "text_delta") {
|
||||
streamedTextIds.add(currentStreamMessageId);
|
||||
} else if (message.event.delta.type === "thinking_delta") {
|
||||
streamedThinkingIds.add(currentStreamMessageId);
|
||||
const delta = message.event.delta;
|
||||
const chunk =
|
||||
delta.type === "text_delta"
|
||||
? { type: "text" as const, text: delta.text }
|
||||
: delta.type === "thinking_delta"
|
||||
? { type: "thinking" as const, text: delta.thinking }
|
||||
: undefined;
|
||||
// Skip empty deltas (some gateways emit empty thinking chunks —
|
||||
// #793): appending "" is a no-op, but pushing a "" entry would
|
||||
// create a block the consolidated handler's `text.length > 0`
|
||||
// guard can never consume, stalling the diff cursor and
|
||||
// re-emitting the next block as a duplicate.
|
||||
if (chunk && chunk.text.length > 0) {
|
||||
const index = message.event.index;
|
||||
const last = streamedBlocks[streamedBlocks.length - 1];
|
||||
if (last && last.index === index && last.type === chunk.type) {
|
||||
last.text += chunk.text;
|
||||
} else {
|
||||
streamedBlocks.push({ index, type: chunk.type, text: chunk.text });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
@@ -1754,6 +1867,7 @@ export class ClaudeAcpAgent {
|
||||
message,
|
||||
params.sessionId,
|
||||
session.toolUseCache,
|
||||
session.waitingForToolCall,
|
||||
this.client,
|
||||
this.logger,
|
||||
{
|
||||
@@ -1854,6 +1968,7 @@ export class ClaudeAcpAgent {
|
||||
message.message.role,
|
||||
params.sessionId,
|
||||
session.toolUseCache,
|
||||
session.waitingForToolCall,
|
||||
this.client,
|
||||
this.logger,
|
||||
{
|
||||
@@ -1907,41 +2022,66 @@ export class ClaudeAcpAgent {
|
||||
|
||||
let content: typeof message.message.content;
|
||||
if (message.type === "assistant" && message.parent_tool_use_id === null) {
|
||||
// Top-level assistant message: drop text/thinking blocks already
|
||||
// streamed live as chunks, and forward (as a fallback) any that were
|
||||
// not, so non-streaming gateways still deliver the final answer.
|
||||
const id = messageIdForGrouping(message);
|
||||
content = message.message.content.filter((item) => {
|
||||
// Non-text blocks (tool_use, etc.) always pass through; their own
|
||||
// dedupe (`toolUseCache`) collapses the streamed/assembled pair.
|
||||
// Top-level assistant message: each text/thinking block may have
|
||||
// already been streamed live as deltas. Diff each against what
|
||||
// streamed (`streamedBlocks`, in document order) and forward only
|
||||
// the un-streamed remainder — nothing if it streamed in full (the
|
||||
// common case), the whole block if it never streamed (a
|
||||
// non-streaming gateway), or just the tail if the stream was cut
|
||||
// short mid-block. `streamPos` walks the streamed blocks in step
|
||||
// with the assembled text/thinking blocks; tool_use and other
|
||||
// blocks pass through untouched (their own `toolUseCache` collapses
|
||||
// the streamed/assembled pair) without advancing it.
|
||||
const blocks = message.message.content;
|
||||
const kept: typeof blocks = [];
|
||||
let streamPos = 0;
|
||||
for (const item of blocks) {
|
||||
if (item.type !== "text" && item.type !== "thinking") {
|
||||
return true;
|
||||
kept.push(item);
|
||||
continue;
|
||||
}
|
||||
// Already delivered live as a chunk of this exact type — drop the
|
||||
// duplicate. Checked per type so streaming one (e.g. text) doesn't
|
||||
// suppress an un-streamed block of the other (e.g. thinking).
|
||||
const streamedLive =
|
||||
id !== undefined &&
|
||||
(item.type === "text" ? streamedTextIds : streamedThinkingIds).has(id);
|
||||
if (streamedLive) {
|
||||
return false;
|
||||
const full = item.type === "text" ? item.text : item.thinking;
|
||||
// Empty assembled blocks carry nothing (some gateways emit an
|
||||
// empty `thinking` block before the real text) — drop them.
|
||||
if (full.length === 0) {
|
||||
continue;
|
||||
}
|
||||
// Empty assembled blocks carry nothing (some gateways emit an empty
|
||||
// `thinking` block before the real text) — don't forward stray
|
||||
// empty chunks.
|
||||
const text = item.type === "text" ? item.text : item.thinking;
|
||||
if (text.length === 0) {
|
||||
return false;
|
||||
// A streamed block of the same type whose accumulated text is a
|
||||
// prefix of this one was already (at least partly) delivered as
|
||||
// chunks; consume it and forward only what's left. A non-empty
|
||||
// streamed text is required so an empty/aborted streamed block
|
||||
// doesn't swallow the assembled copy.
|
||||
const streamed = streamedBlocks[streamPos];
|
||||
if (
|
||||
streamed &&
|
||||
streamed.type === item.type &&
|
||||
streamed.text.length > 0 &&
|
||||
full.startsWith(streamed.text)
|
||||
) {
|
||||
streamPos++;
|
||||
const remainder = full.slice(streamed.text.length);
|
||||
if (remainder.length === 0) {
|
||||
continue;
|
||||
}
|
||||
// Overwrite in place with just the un-streamed tail (the
|
||||
// assembled message isn't read again after this) so the block
|
||||
// keeps its exact SDK type.
|
||||
if (item.type === "text") {
|
||||
item.text = remainder;
|
||||
} else {
|
||||
item.thinking = remainder;
|
||||
}
|
||||
kept.push(item);
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// The consolidated message is the last place this id is needed for
|
||||
// dedupe — drop it so the streamed-id sets stay bounded to in-flight
|
||||
// streams rather than growing for the session's whole life.
|
||||
if (id !== undefined) {
|
||||
streamedTextIds.delete(id);
|
||||
streamedThinkingIds.delete(id);
|
||||
// Not matched: never streamed (or the stream diverged from the
|
||||
// assembled text) — forward the block in full.
|
||||
kept.push(item);
|
||||
}
|
||||
content = kept;
|
||||
// Consumed: reset so the next message's blocks accumulate fresh and
|
||||
// the record stays bounded to the in-flight message.
|
||||
streamedBlocks.length = 0;
|
||||
} else if (message.type === "assistant") {
|
||||
// Subagent assistant message (`parent_tool_use_id !== null`). It is
|
||||
// never streamed live and its text/thinking is internal to the tool
|
||||
@@ -1959,6 +2099,7 @@ export class ClaudeAcpAgent {
|
||||
message.message.role,
|
||||
params.sessionId,
|
||||
session.toolUseCache,
|
||||
session.waitingForToolCall,
|
||||
this.client,
|
||||
this.logger,
|
||||
{
|
||||
@@ -2326,6 +2467,7 @@ export class ClaudeAcpAgent {
|
||||
|
||||
private async replaySessionHistory(sessionId: string): Promise<void> {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const messages = await getSessionMessages(sessionId);
|
||||
|
||||
for (const message of messages) {
|
||||
@@ -2354,6 +2496,7 @@ export class ClaudeAcpAgent {
|
||||
message.message.role,
|
||||
sessionId,
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
this.client,
|
||||
this.logger,
|
||||
{
|
||||
@@ -2379,10 +2522,29 @@ export class ClaudeAcpAgent {
|
||||
return response;
|
||||
}
|
||||
|
||||
/** Forward a permission request to the client, wiring the tool call's
|
||||
* `signal` through as a `cancellationSignal`. When the turn is cancelled
|
||||
* while the client's prompt is still open the signal aborts, the SDK sends
|
||||
* `$/cancel_request`, and the client settles the request (a `cancelled`
|
||||
* outcome or a `requestCancelled` rejection). Either way we surface the same
|
||||
* "Tool use aborted" the callers already expect, so a cancelled dialog no
|
||||
* longer leaves the `await` hanging. */
|
||||
private async requestPermissionFromClient(
|
||||
params: RequestPermissionRequest,
|
||||
signal: AbortSignal,
|
||||
): Promise<RequestPermissionResponse> {
|
||||
try {
|
||||
return await this.client.requestPermission(params, signal);
|
||||
} catch (error) {
|
||||
if (signal.aborted) {
|
||||
throw new Error("Tool use aborted", { cause: error });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
canUseTool(sessionId: string): CanUseTool {
|
||||
return async (toolName, toolInput, { signal, suggestions, toolUseID }) => {
|
||||
const alwaysAllowLabel = describeAlwaysAllow(suggestions, toolName);
|
||||
const supportsTerminalOutput = this.clientCapabilities?._meta?.["terminal_output"] === true;
|
||||
const session = this.sessions[sessionId];
|
||||
if (!session) {
|
||||
return {
|
||||
@@ -2390,6 +2552,13 @@ export class ClaudeAcpAgent {
|
||||
message: "Session not found",
|
||||
};
|
||||
}
|
||||
if (!session.toolUseCache[toolUseID]) {
|
||||
const { promise, resolve } = Promise.withResolvers<void>();
|
||||
session.waitingForToolCall.set(toolUseID, resolve);
|
||||
await promise;
|
||||
}
|
||||
const alwaysAllowLabel = describeAlwaysAllow(suggestions, toolName);
|
||||
const supportsTerminalOutput = this.clientCapabilities?._meta?.["terminal_output"] === true;
|
||||
|
||||
// AskUserQuestion is surfaced to us as a normal permission check (the SDK
|
||||
// routes it through canUseTool whenever a callback is registered, rather
|
||||
@@ -2426,19 +2595,22 @@ export class ClaudeAcpAgent {
|
||||
session.modes.availableModes.some((m) => m.id === o.optionId),
|
||||
);
|
||||
|
||||
const response = await this.client.requestPermission({
|
||||
options,
|
||||
sessionId,
|
||||
toolCall: {
|
||||
toolCallId: toolUseID,
|
||||
rawInput: toolInput,
|
||||
...toolInfoFromToolUse(
|
||||
{ name: toolName, input: toolInput, id: toolUseID },
|
||||
supportsTerminalOutput,
|
||||
session?.cwd,
|
||||
),
|
||||
const response = await this.requestPermissionFromClient(
|
||||
{
|
||||
options,
|
||||
sessionId,
|
||||
toolCall: {
|
||||
toolCallId: toolUseID,
|
||||
rawInput: toolInput,
|
||||
...toolInfoFromToolUse(
|
||||
{ name: toolName, input: toolInput, id: toolUseID },
|
||||
supportsTerminalOutput,
|
||||
session?.cwd,
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
signal,
|
||||
);
|
||||
|
||||
if (signal.aborted || response.outcome?.outcome === "cancelled") {
|
||||
throw new Error("Tool use aborted");
|
||||
@@ -2487,27 +2659,30 @@ export class ClaudeAcpAgent {
|
||||
};
|
||||
}
|
||||
|
||||
const response = await this.client.requestPermission({
|
||||
options: [
|
||||
{
|
||||
kind: "allow_always",
|
||||
name: alwaysAllowLabel,
|
||||
optionId: "allow_always",
|
||||
const response = await this.requestPermissionFromClient(
|
||||
{
|
||||
options: [
|
||||
{
|
||||
kind: "allow_always",
|
||||
name: alwaysAllowLabel,
|
||||
optionId: "allow_always",
|
||||
},
|
||||
{ kind: "allow_once", name: "Allow", optionId: "allow" },
|
||||
{ kind: "reject_once", name: "Reject", optionId: "reject" },
|
||||
],
|
||||
sessionId,
|
||||
toolCall: {
|
||||
toolCallId: toolUseID,
|
||||
rawInput: toolInput,
|
||||
...toolInfoFromToolUse(
|
||||
{ name: toolName, input: toolInput, id: toolUseID },
|
||||
supportsTerminalOutput,
|
||||
session?.cwd,
|
||||
),
|
||||
},
|
||||
{ kind: "allow_once", name: "Allow", optionId: "allow" },
|
||||
{ kind: "reject_once", name: "Reject", optionId: "reject" },
|
||||
],
|
||||
sessionId,
|
||||
toolCall: {
|
||||
toolCallId: toolUseID,
|
||||
rawInput: toolInput,
|
||||
...toolInfoFromToolUse(
|
||||
{ name: toolName, input: toolInput, id: toolUseID },
|
||||
supportsTerminalOutput,
|
||||
session?.cwd,
|
||||
),
|
||||
},
|
||||
});
|
||||
signal,
|
||||
);
|
||||
if (signal.aborted || response.outcome?.outcome === "cancelled") {
|
||||
throw new Error("Tool use aborted");
|
||||
}
|
||||
@@ -2561,12 +2736,17 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.client.unstable_createElicitation(createRequest);
|
||||
const response = await this.client.unstable_createElicitation(createRequest, signal);
|
||||
if (signal.aborted) {
|
||||
return { action: "cancel" };
|
||||
}
|
||||
return createElicitationResponseToElicitResult(response);
|
||||
} catch (error) {
|
||||
// A cancellation we requested (signal aborted) settles as a cancel, not
|
||||
// a hard decline — the elicitation was abandoned, not refused.
|
||||
if (signal.aborted) {
|
||||
return { action: "cancel" };
|
||||
}
|
||||
this.logger.error(`Failed to forward MCP elicitation: ${error}`);
|
||||
return { action: "decline" };
|
||||
}
|
||||
@@ -2592,8 +2772,13 @@ export class ClaudeAcpAgent {
|
||||
const createRequest = askUserQuestionsToCreateRequest(questions, sessionId, toolUseID);
|
||||
let response;
|
||||
try {
|
||||
response = await this.client.unstable_createElicitation(createRequest);
|
||||
response = await this.client.unstable_createElicitation(createRequest, signal);
|
||||
} catch (error) {
|
||||
// A cancellation we requested (signal aborted) settles as an aborted tool
|
||||
// use, matching the post-response check below.
|
||||
if (signal.aborted) {
|
||||
throw new Error("Tool use aborted", { cause: error });
|
||||
}
|
||||
this.logger.error(`Failed to present AskUserQuestion elicitation: ${error}`);
|
||||
return { behavior: "deny", message: "Could not present the question to the user." };
|
||||
}
|
||||
@@ -2652,19 +2837,27 @@ export class ClaudeAcpAgent {
|
||||
o.id === configId && typeof o.currentValue === "string" ? { ...o, currentValue: value } : o,
|
||||
);
|
||||
} else if (configId === "model") {
|
||||
// `ModelInfo.supportsAutoMode` is the canonical SDK signal for clamping
|
||||
// modes below; its `displayName`/`description` also let us infer the
|
||||
// context window for semantic aliases (e.g. `default`) whose ID alone
|
||||
// carries no "1m" token.
|
||||
const newModelInfo = session.modelInfos.find((m) => m.value === value);
|
||||
if (session.models.currentModelId !== value) {
|
||||
// The cached context window was learned for the previous model; reset
|
||||
// to the new model's heuristic so mid-stream updates between now and
|
||||
// the next `result` reflect the user's selection instead of the old
|
||||
// model's window.
|
||||
session.contextWindowSize = inferContextWindowFromModel(value) ?? DEFAULT_CONTEXT_WINDOW;
|
||||
session.contextWindowSize =
|
||||
inferContextWindowFromModel(
|
||||
value,
|
||||
newModelInfo?.displayName,
|
||||
newModelInfo?.description,
|
||||
) ?? DEFAULT_CONTEXT_WINDOW;
|
||||
}
|
||||
session.models = { ...session.models, currentModelId: value };
|
||||
|
||||
// Recompute availableModes for the new model and clamp the current
|
||||
// mode if the SDK no longer offers it (today: "auto" on Haiku).
|
||||
// `ModelInfo.supportsAutoMode` is the canonical SDK signal.
|
||||
const newModelInfo = session.modelInfos.find((m) => m.value === value);
|
||||
const newAvailableModes = buildAvailableModes(newModelInfo);
|
||||
// Capture BEFORE mutating session.modes so the log message reflects
|
||||
// the invalidated mode rather than "default".
|
||||
@@ -3219,9 +3412,14 @@ export class ClaudeAcpAgent {
|
||||
abortController,
|
||||
emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false,
|
||||
contextWindowSize:
|
||||
inferContextWindowFromModel(models.currentModelId) ?? DEFAULT_CONTEXT_WINDOW,
|
||||
inferContextWindowFromModel(
|
||||
models.currentModelId,
|
||||
currentModelInfo?.displayName,
|
||||
currentModelInfo?.description,
|
||||
) ?? DEFAULT_CONTEXT_WINDOW,
|
||||
taskState,
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
};
|
||||
|
||||
@@ -3947,6 +4145,7 @@ export function toAcpNotifications(
|
||||
role: "assistant" | "user",
|
||||
sessionId: string,
|
||||
toolUseCache: ToolUseCache,
|
||||
waitingForToolCall: WaitingForToolCall,
|
||||
client: AcpClient,
|
||||
logger: Logger,
|
||||
options?: {
|
||||
@@ -4116,6 +4315,13 @@ export function toAcpNotifications(
|
||||
...toolInfoFromToolUse(chunk, supportsTerminalOutput, options?.cwd),
|
||||
};
|
||||
} else {
|
||||
if (waitingForToolCall.has(chunk.id)) {
|
||||
// If a canUseTool is waiting for the first tool_use chunk,
|
||||
// we can resolve it now.
|
||||
const resolve = waitingForToolCall.get(chunk.id)!;
|
||||
resolve();
|
||||
waitingForToolCall.delete(chunk.id);
|
||||
}
|
||||
// First encounter (streaming content_block_start or replay) —
|
||||
// send as tool_call with terminal_info for Bash tools.
|
||||
update = {
|
||||
@@ -4273,6 +4479,7 @@ export function streamEventToAcpNotifications(
|
||||
message: SDKPartialAssistantMessage,
|
||||
sessionId: string,
|
||||
toolUseCache: ToolUseCache,
|
||||
waitingForToolCall: WaitingForToolCall,
|
||||
client: AcpClient,
|
||||
logger: Logger,
|
||||
options?: {
|
||||
@@ -4290,6 +4497,7 @@ export function streamEventToAcpNotifications(
|
||||
"assistant",
|
||||
sessionId,
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
client,
|
||||
logger,
|
||||
{
|
||||
@@ -4306,6 +4514,7 @@ export function streamEventToAcpNotifications(
|
||||
"assistant",
|
||||
sessionId,
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
client,
|
||||
logger,
|
||||
{
|
||||
@@ -4333,6 +4542,38 @@ export function streamEventToAcpNotifications(
|
||||
}
|
||||
}
|
||||
|
||||
/** Run a `session/prompt` while honoring `$/cancel_request` for it. ACP clients
|
||||
* normally stop a turn with the `session/cancel` notification, but `signal`
|
||||
* (the prompt request's abort signal) also fires when the client sends the
|
||||
* generic `$/cancel_request` for this prompt — the protocol's complementary
|
||||
* cancellation fallback. Route that to the same `agent.cancel` path so a client
|
||||
* using only the generic mechanism still stops the turn (and the prompt
|
||||
* resolves "cancelled" instead of running to completion).
|
||||
*
|
||||
* The listener is scoped to this call: once the prompt settles it is removed,
|
||||
* so a later teardown-time abort of the (per-request) signal can't cancel a
|
||||
* subsequent turn. `signal` also aborts on connection close, in which case
|
||||
* cancelling the in-flight turn is the desired behavior anyway. */
|
||||
export async function runPromptWithCancellation(
|
||||
agent: Pick<ClaudeAcpAgent, "prompt" | "cancel" | "logger">,
|
||||
params: PromptRequest,
|
||||
signal: AbortSignal,
|
||||
): Promise<PromptResponse> {
|
||||
const onAbort = () => {
|
||||
// Fire-and-forget: nothing awaits this listener, so swallow (and log) any
|
||||
// rejection rather than surfacing it as an unhandled rejection.
|
||||
agent.cancel({ sessionId: params.sessionId }).catch((error) => {
|
||||
agent.logger.error(`Failed to cancel prompt via $/cancel_request: ${error}`);
|
||||
});
|
||||
};
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
try {
|
||||
return await agent.prompt(params);
|
||||
} finally {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
}
|
||||
}
|
||||
|
||||
export function runAcp() {
|
||||
const input = nodeToWebWritable(process.stdout);
|
||||
const output = nodeToWebReadable(process.stdin);
|
||||
@@ -4361,7 +4602,9 @@ export function runAcp() {
|
||||
agent.setSessionConfigOption(ctx.params),
|
||||
)
|
||||
.onRequest(methods.agent.authenticate, (ctx) => agent.authenticate(ctx.params))
|
||||
.onRequest(methods.agent.session.prompt, (ctx) => agent.prompt(ctx.params))
|
||||
.onRequest(methods.agent.session.prompt, (ctx) =>
|
||||
runPromptWithCancellation(agent, ctx.params, ctx.signal),
|
||||
)
|
||||
.onNotification(methods.agent.session.cancel, (ctx) => agent.cancel(ctx.params))
|
||||
.connect(stream);
|
||||
|
||||
@@ -4377,14 +4620,21 @@ function commonPrefixLength(a: string, b: string) {
|
||||
return i;
|
||||
}
|
||||
|
||||
/** Best-effort first guess of a model's context window from its ID, used only
|
||||
* as a fallback when the SDK's authoritative `getContextUsage` is unavailable
|
||||
* (and until a `result` message arrives with the `modelUsage` value).
|
||||
/** Best-effort first guess of a model's context window, used only as a
|
||||
* fallback when the SDK's authoritative `getContextUsage` is unavailable (and
|
||||
* until a `result` message arrives with the `modelUsage` value).
|
||||
*
|
||||
* Anthropic 1M-context variants encode "1m" as a distinct token in the SDK
|
||||
* model ID (e.g., "claude-opus-4-6-1m"), which `\b1m\b` catches without also
|
||||
* matching things like "10m" or embedded substrings. */
|
||||
function inferContextWindowFromModel(model: string): number | null {
|
||||
if (/\b1m\b/i.test(model)) return 1_000_000;
|
||||
* matching things like "10m" or embedded substrings. Semantic aliases like
|
||||
* `default` carry no such token in the ID, but the SDK's human-facing
|
||||
* `displayName`/`description` do (e.g. "Opus 4.7 (1M context)"), so callers
|
||||
* pass those too — the same `\b1m\b` token appears in "1M context". The SDK's
|
||||
* `ModelInfo` exposes no structured context-window field, so this text scan is
|
||||
* the only pre-`result` signal available. A miss falls back to the default
|
||||
* window and is corrected by `result.modelUsage` within one turn. */
|
||||
function inferContextWindowFromModel(...texts: Array<string | undefined>): number | null {
|
||||
if (texts.some((text) => text != null && /\b1m\b/i.test(text))) return 1_000_000;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
import { resolveSettings } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { claudeCliPath, runAcp } from "./acp-agent.js";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
|
||||
if (process.argv.includes("--cli")) {
|
||||
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
||||
console.log(packageJson.version);
|
||||
process.exit(0);
|
||||
} else if (process.argv.includes("--cli")) {
|
||||
const { spawn } = await import("node:child_process");
|
||||
const args = process.argv.slice(2).filter((arg) => arg !== "--cli");
|
||||
const child = spawn(await claudeCliPath(), args, { stdio: "inherit" });
|
||||
|
||||
@@ -38,12 +38,14 @@ import {
|
||||
messageIdForGrouping,
|
||||
buildConfigOptions,
|
||||
discoverCustomAgents,
|
||||
runPromptWithCancellation,
|
||||
type AcpClient,
|
||||
type SDKMessageFilter,
|
||||
} from "../acp-agent.js";
|
||||
import { Pushable } from "../utils.js";
|
||||
import {
|
||||
deleteSession,
|
||||
getSessionInfo,
|
||||
getSessionMessages,
|
||||
query,
|
||||
SDKAssistantMessage,
|
||||
@@ -55,6 +57,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
deleteSession: vi.fn(),
|
||||
getSessionInfo: vi.fn(),
|
||||
};
|
||||
});
|
||||
import type {
|
||||
@@ -115,6 +118,7 @@ function mockSessionState(overrides: Record<string, any> = {}) {
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
...overrides,
|
||||
} as any;
|
||||
@@ -869,6 +873,7 @@ describe("tool conversions", () => {
|
||||
received.message.role,
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
),
|
||||
@@ -1783,7 +1788,207 @@ describe("permission requests", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("permission request cancellation", () => {
|
||||
function injectSession(agent: ClaudeAcpAgent, sessionId: string) {
|
||||
function* empty() {}
|
||||
const gen = Object.assign(empty(), { interrupt: vi.fn(), close: vi.fn() });
|
||||
agent.sessions[sessionId] = {
|
||||
query: gen as any,
|
||||
input: new Pushable(),
|
||||
cancelled: false,
|
||||
cwd: "/test",
|
||||
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
|
||||
modes: { currentModeId: "default", availableModes: [] },
|
||||
models: { currentModelId: "default", availableModels: [] },
|
||||
modelInfos: [],
|
||||
settingsManager: { dispose: vi.fn() } as any,
|
||||
accumulatedUsage: {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
cachedReadTokens: 0,
|
||||
cachedWriteTokens: 0,
|
||||
},
|
||||
configOptions: [],
|
||||
agents: [],
|
||||
currentAgent: "default",
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
} as any;
|
||||
return agent.sessions[sessionId]!;
|
||||
}
|
||||
|
||||
it("waits for the first tool_call before requesting permission", async () => {
|
||||
const requestPermission = vi.fn(async () => ({
|
||||
outcome: { outcome: "selected", optionId: "allow" },
|
||||
}));
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
requestPermission,
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
const session = injectSession(agent, "session-1");
|
||||
|
||||
const pending = agent.canUseTool("session-1")("Bash", { command: "echo hello" }, {
|
||||
signal: new AbortController().signal,
|
||||
suggestions: [],
|
||||
toolUseID: "tool-1",
|
||||
} as any);
|
||||
|
||||
await Promise.resolve();
|
||||
expect(requestPermission).not.toHaveBeenCalled();
|
||||
expect(session.waitingForToolCall.has("tool-1")).toBe(true);
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[{ type: "tool_use", id: "tool-1", name: "Bash", input: { command: "echo hello" } }],
|
||||
"assistant",
|
||||
"session-1",
|
||||
session.toolUseCache,
|
||||
session.waitingForToolCall,
|
||||
mockClient,
|
||||
console,
|
||||
);
|
||||
|
||||
expect(notifications[0].update.sessionUpdate).toBe("tool_call");
|
||||
expect(requestPermission).not.toHaveBeenCalled();
|
||||
|
||||
await expect(pending).resolves.toMatchObject({ behavior: "allow" });
|
||||
expect(requestPermission).toHaveBeenCalledTimes(1);
|
||||
expect(session.waitingForToolCall.has("tool-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("forwards the tool-call signal so a pending permission request is cancelled on abort", async () => {
|
||||
let receivedSignal: AbortSignal | undefined;
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
// A `$/cancel_request`-aware client settles the request once the agent
|
||||
// aborts it; model that by rejecting when the forwarded signal fires.
|
||||
requestPermission: (_params: RequestPermissionRequest, signal?: AbortSignal) => {
|
||||
receivedSignal = signal;
|
||||
return new Promise<RequestPermissionResponse>((_resolve, reject) => {
|
||||
signal?.addEventListener("abort", () => reject(new Error("Request cancelled")), {
|
||||
once: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
const session = injectSession(agent, "session-1");
|
||||
session.toolUseCache["tool-1"] = {
|
||||
type: "tool_use",
|
||||
id: "tool-1",
|
||||
name: "Bash",
|
||||
input: { command: "ls" },
|
||||
};
|
||||
|
||||
const controller = new AbortController();
|
||||
const pending = agent.canUseTool("session-1")("Bash", { command: "ls" }, {
|
||||
signal: controller.signal,
|
||||
suggestions: [],
|
||||
toolUseID: "tool-1",
|
||||
} as any);
|
||||
// Let canUseTool reach the awaited requestPermission before cancelling.
|
||||
await Promise.resolve();
|
||||
|
||||
// The tool-call signal is threaded through as the cancellation signal.
|
||||
expect(receivedSignal).toBe(controller.signal);
|
||||
|
||||
controller.abort();
|
||||
|
||||
await expect(pending).rejects.toThrow("Tool use aborted");
|
||||
});
|
||||
|
||||
it("treats a cancelled permission outcome as an aborted tool use", async () => {
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
const session = injectSession(agent, "session-1");
|
||||
session.toolUseCache["tool-1"] = {
|
||||
type: "tool_use",
|
||||
id: "tool-1",
|
||||
name: "Bash",
|
||||
input: { command: "ls" },
|
||||
};
|
||||
|
||||
await expect(
|
||||
agent.canUseTool("session-1")("Bash", { command: "ls" }, {
|
||||
signal: new AbortController().signal,
|
||||
suggestions: [],
|
||||
toolUseID: "tool-1",
|
||||
} as any),
|
||||
).rejects.toThrow("Tool use aborted");
|
||||
});
|
||||
});
|
||||
|
||||
describe("runPromptWithCancellation", () => {
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>((r) => {
|
||||
resolve = r;
|
||||
});
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
it("cancels the in-flight prompt when the request signal aborts ($/cancel_request)", async () => {
|
||||
const promptResult = deferred<PromptResponse>();
|
||||
const cancel = vi.fn(async () => {});
|
||||
const agent = {
|
||||
prompt: vi.fn(() => promptResult.promise),
|
||||
cancel,
|
||||
logger: { log: () => {}, error: () => {} },
|
||||
} as any;
|
||||
|
||||
const controller = new AbortController();
|
||||
const params = { sessionId: "session-1", prompt: [] } as any;
|
||||
const pending = runPromptWithCancellation(agent, params, controller.signal);
|
||||
|
||||
// No cancel yet — the turn is running.
|
||||
expect(cancel).not.toHaveBeenCalled();
|
||||
|
||||
// Client sends $/cancel_request -> the SDK aborts this request's signal.
|
||||
controller.abort();
|
||||
expect(cancel).toHaveBeenCalledWith({ sessionId: "session-1" });
|
||||
|
||||
// The prompt settles "cancelled" through the normal cancel path.
|
||||
promptResult.resolve({ stopReason: "cancelled" });
|
||||
await expect(pending).resolves.toEqual({ stopReason: "cancelled" });
|
||||
});
|
||||
|
||||
it("does not cancel after the prompt settles normally", async () => {
|
||||
const promptResult = deferred<PromptResponse>();
|
||||
const cancel = vi.fn(async () => {});
|
||||
const agent = {
|
||||
prompt: vi.fn(() => promptResult.promise),
|
||||
cancel,
|
||||
logger: { log: () => {}, error: () => {} },
|
||||
} as any;
|
||||
|
||||
const controller = new AbortController();
|
||||
const params = { sessionId: "session-1", prompt: [] } as any;
|
||||
const pending = runPromptWithCancellation(agent, params, controller.signal);
|
||||
|
||||
promptResult.resolve({ stopReason: "end_turn" });
|
||||
await expect(pending).resolves.toEqual({ stopReason: "end_turn" });
|
||||
|
||||
// A late abort (e.g. per-request signal cleanup) must not cancel a later turn.
|
||||
controller.abort();
|
||||
expect(cancel).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("stop reason propagation", () => {
|
||||
// The title-update tests set `getSessionInfo` to resolve a title; reset it so
|
||||
// that value can't leak into other turn-end (idle) assertions in this block.
|
||||
beforeEach(() => {
|
||||
vi.mocked(getSessionInfo).mockReset();
|
||||
});
|
||||
|
||||
function createMockAgent() {
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
@@ -2179,6 +2384,98 @@ describe("stop reason propagation", () => {
|
||||
expect(chunkTexts).toContain("between-turn background note");
|
||||
});
|
||||
|
||||
it("pushes a session_info_update when the SDK generates a title at turn-end", async () => {
|
||||
const sessionUpdates: any[] = [];
|
||||
const mockClient = {
|
||||
sessionUpdate: async (u: any) => {
|
||||
sessionUpdates.push(u);
|
||||
},
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
|
||||
vi.mocked(getSessionInfo).mockResolvedValue({
|
||||
sessionId: "test-session",
|
||||
summary: "Fix the flaky title test",
|
||||
lastModified: 1_700_000_000_000,
|
||||
} as any);
|
||||
|
||||
const input = new Pushable<any>();
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const { value: userMessage } = await iter.next();
|
||||
yield userEcho(userMessage);
|
||||
yield createResultMessage({ subtype: "success", stop_reason: "end_turn", is_error: false });
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" };
|
||||
}
|
||||
|
||||
agent.sessions["test-session"] = mockSessionState({
|
||||
query: wrapQuery(messageGenerator()),
|
||||
input,
|
||||
});
|
||||
|
||||
await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "test" }],
|
||||
});
|
||||
await agent.sessions["test-session"]?.consumer;
|
||||
|
||||
const titleUpdate = sessionUpdates.find(
|
||||
(u) => u.update?.sessionUpdate === "session_info_update",
|
||||
);
|
||||
expect(titleUpdate?.update).toEqual({
|
||||
sessionUpdate: "session_info_update",
|
||||
title: "Fix the flaky title test",
|
||||
updatedAt: new Date(1_700_000_000_000).toISOString(),
|
||||
});
|
||||
expect(getSessionInfo).toHaveBeenCalledWith("test-session", { dir: "/test" });
|
||||
});
|
||||
|
||||
it("does not re-push session_info_update when the title is unchanged", async () => {
|
||||
const sessionUpdates: any[] = [];
|
||||
const mockClient = {
|
||||
sessionUpdate: async (u: any) => {
|
||||
sessionUpdates.push(u);
|
||||
},
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
|
||||
vi.mocked(getSessionInfo).mockResolvedValue({
|
||||
sessionId: "test-session",
|
||||
summary: "Stable title",
|
||||
lastModified: 1_700_000_000_000,
|
||||
} as any);
|
||||
|
||||
const input = new Pushable<any>();
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
// Two turns, each ending in idle, but the title never changes.
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const { value: userMessage } = await iter.next();
|
||||
yield userEcho(userMessage);
|
||||
yield createResultMessage({
|
||||
subtype: "success",
|
||||
stop_reason: "end_turn",
|
||||
is_error: false,
|
||||
});
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" };
|
||||
}
|
||||
}
|
||||
|
||||
agent.sessions["test-session"] = mockSessionState({
|
||||
query: wrapQuery(messageGenerator()),
|
||||
input,
|
||||
});
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "one" }] });
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "two" }] });
|
||||
await agent.sessions["test-session"]?.consumer;
|
||||
|
||||
const titleUpdates = sessionUpdates.filter(
|
||||
(u) => u.update?.sessionUpdate === "session_info_update",
|
||||
);
|
||||
expect(titleUpdates).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should throw internal error for success with is_error true and no max_tokens", async () => {
|
||||
const agent = createMockAgent();
|
||||
injectSession(agent, [
|
||||
@@ -2321,6 +2618,7 @@ describe("session/close", () => {
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
@@ -2406,6 +2704,7 @@ describe("session/delete", () => {
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
@@ -2508,6 +2807,7 @@ describe("getOrCreateSession param change detection", () => {
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
@@ -3072,6 +3372,26 @@ describe("usage_update computation", () => {
|
||||
expect(usageUpdates[1].update.size).toBe(1000000);
|
||||
});
|
||||
|
||||
it("infers the 1M window from a model's description when the ID lacks a 1m token (issue #596)", async () => {
|
||||
// Semantic aliases like `default` resolve to a 1M-context model but carry
|
||||
// no "1m" token in the modelId — the SDK signals 1M only via the
|
||||
// human-facing displayName/description (e.g. "Opus 4.7 with 1M context").
|
||||
// Inference must read those so the session reports the correct window from
|
||||
// the first mid-stream update instead of the 200k placeholder.
|
||||
const { agent } = createMockAgentWithCapture();
|
||||
injectSession(agent, [{ type: "system", subtype: "session_state_changed", state: "idle" }]);
|
||||
const session = agent.sessions["test-session"];
|
||||
session.models = { currentModelId: "claude-sonnet-4-6", availableModels: [] };
|
||||
session.modelInfos = [
|
||||
{ value: "default", displayName: "Default", description: "Opus 4.7 with 1M context" },
|
||||
] as any;
|
||||
expect(session.contextWindowSize).toBe(200000);
|
||||
|
||||
await (agent as any).applyConfigOptionValue("test-session", session, "model", "default");
|
||||
|
||||
expect(session.contextWindowSize).toBe(1000000);
|
||||
});
|
||||
|
||||
it("result with no matching modelUsage preserves the learned window", async () => {
|
||||
// A turn whose `result.modelUsage` doesn't contain the current top-level
|
||||
// model (e.g. no top-level assistant message, or only a subagent ran) must
|
||||
@@ -3872,6 +4192,134 @@ describe("assembled assistant text fallback", () => {
|
||||
expect(thoughtChunkTexts(updates)).toEqual(["private reasoning"]);
|
||||
});
|
||||
|
||||
it("forwards only the un-streamed remainder when the stream is cut short mid-block", async () => {
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
// The stream stops partway ("hello ") but the consolidated message carries
|
||||
// the whole block ("hello world"). The streamed prefix must not be re-sent,
|
||||
// and the un-streamed tail must still reach the client — dropping the whole
|
||||
// assembled block would truncate the answer to "hello ".
|
||||
injectSession(agent, [
|
||||
messageStart("msg-partial"),
|
||||
textDelta("hello "),
|
||||
assistantMessage("msg-partial", [{ type: "text", text: "hello world" }]),
|
||||
result(),
|
||||
idle,
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
|
||||
|
||||
// The streamed prefix, then just the tail from the consolidated message.
|
||||
expect(messageChunkTexts(updates)).toEqual(["hello ", "world"]);
|
||||
});
|
||||
|
||||
it("dedupes streamed text even when the consolidated message carries a different id", async () => {
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
// Some gateways assign one id during the stream and a different one (or only
|
||||
// a uuid) on the assembled message. Dedupe must key on content, not the id,
|
||||
// or the consolidated block re-emits already-streamed text as a duplicate.
|
||||
injectSession(agent, [
|
||||
messageStart("msg-stream-id"),
|
||||
textDelta("hello "),
|
||||
textDelta("world"),
|
||||
assistantMessage("msg-DIFFERENT-id", [{ type: "text", text: "hello world" }]),
|
||||
result(),
|
||||
idle,
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
|
||||
|
||||
// Only the streamed deltas — the assembled copy is deduped despite the id
|
||||
// mismatch.
|
||||
expect(messageChunkTexts(updates)).toEqual(["hello ", "world"]);
|
||||
});
|
||||
|
||||
it("dedupes a streamed text block even when an empty thinking delta precedes it", async () => {
|
||||
// An empty thinking delta (some gateways emit them — #793) must not create
|
||||
// a zero-length streamedBlocks entry: that entry can never satisfy the
|
||||
// consolidated handler's `text.length > 0` guard, so it would stall the
|
||||
// diff cursor and re-emit the real, already-streamed text as a duplicate.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
messageStart("msg-empty-thinking"),
|
||||
thinkingDelta(""),
|
||||
textDelta("real answer"),
|
||||
assistantMessage("msg-empty-thinking", [{ type: "text", text: "real answer" }]),
|
||||
result(),
|
||||
idle,
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
|
||||
|
||||
// The streamed text appears once; the consolidated copy is deduped.
|
||||
expect(messageChunkTexts(updates)).toEqual(["real answer"]);
|
||||
});
|
||||
|
||||
it("does not re-emit the next turn's text after a turn is cancelled mid-stream", async () => {
|
||||
// Regression: streamedBlocks is reset inside the consolidated-assistant
|
||||
// branch, but a cancelled turn `break`s out before reaching it (the
|
||||
// `if (session.cancelled) break;` guard), and streamedBlocks is
|
||||
// session-scoped — so a cancelled turn's streamed text used to leak into
|
||||
// the next turn. Block indices restart at 0 per message, so the leftover
|
||||
// "Hello there" would fuse with turn 2's first block and make its
|
||||
// consolidated copy fail the prefix dedupe, re-emitting "Second answer" as
|
||||
// a duplicate. The fix resets streamedBlocks on each top-level
|
||||
// `message_start`, bounding the record to one in-flight message.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
|
||||
let releaseCancel!: () => void;
|
||||
const cancelled = new Promise<void>((resolve) => {
|
||||
releaseCancel = resolve;
|
||||
});
|
||||
|
||||
injectGeneratorSession(agent, (input) => {
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const u1 = await iter.next();
|
||||
yield userEcho(u1.value); // activate turn 1
|
||||
yield messageStart("msg-1");
|
||||
yield textDelta("Hello ");
|
||||
yield textDelta("there"); // streamedBlocks = [{ index: 0, text: "Hello there" }]
|
||||
await cancelled; // hold until the test has cancelled turn 1
|
||||
// Turn 1's consolidated message arrives while cancelled → hits the
|
||||
// `if (session.cancelled) break;` guard, skipping the streamedBlocks
|
||||
// reset. The leftover entry must not survive into turn 2.
|
||||
yield assistantMessage("msg-1", [{ type: "text", text: "Hello there" }]);
|
||||
yield idle; // settles turn 1 as cancelled
|
||||
const u2 = await iter.next();
|
||||
yield userEcho(u2.value); // activate turn 2
|
||||
yield messageStart("msg-2"); // resets streamedBlocks (the fix)
|
||||
yield textDelta("Second answer");
|
||||
yield assistantMessage("msg-2", [{ type: "text", text: "Second answer" }]);
|
||||
yield result();
|
||||
yield idle;
|
||||
}
|
||||
return messageGenerator();
|
||||
});
|
||||
|
||||
const first = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "first" }],
|
||||
});
|
||||
// Wait until turn 1's deltas have streamed before cancelling.
|
||||
const deadline = Date.now() + 1000;
|
||||
while (!messageChunkTexts(updates).includes("there")) {
|
||||
if (Date.now() > deadline) throw new Error("turn 1 stream never arrived");
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
}
|
||||
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
releaseCancel();
|
||||
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "second" }] });
|
||||
|
||||
// Turn 2's text appears exactly once (the live delta); the consolidated copy
|
||||
// is deduped despite the cancelled turn's leftover streamed text.
|
||||
expect(messageChunkTexts(updates).filter((t) => t === "Second answer")).toEqual([
|
||||
"Second answer",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not leak subagent assistant text into the top-level feed", async () => {
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
// Subagent assistant messages (parent_tool_use_id !== null) are never
|
||||
@@ -4574,6 +5022,7 @@ describe("post-error recovery", () => {
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
};
|
||||
return { interrupt };
|
||||
@@ -5144,6 +5593,7 @@ describe("session/cancel wedge recovery (issue #680)", () => {
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
};
|
||||
return { interrupt };
|
||||
@@ -5275,9 +5725,8 @@ describe("streamEventToAcpNotifications", () => {
|
||||
pingMessage,
|
||||
"test-session",
|
||||
{},
|
||||
{ sessionUpdate: async () => {} } as unknown as Parameters<
|
||||
typeof streamEventToAcpNotifications
|
||||
>[3],
|
||||
new Map(),
|
||||
{ sessionUpdate: async () => {} } as unknown as AcpClient,
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -5299,9 +5748,17 @@ describe("streamEventToAcpNotifications", () => {
|
||||
},
|
||||
} as Parameters<typeof streamEventToAcpNotifications>[0];
|
||||
|
||||
const result = streamEventToAcpNotifications(message, "test", {}, {} as AcpClient, console, {
|
||||
messageId,
|
||||
});
|
||||
const result = streamEventToAcpNotifications(
|
||||
message,
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
{
|
||||
messageId,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
@@ -5325,6 +5782,7 @@ describe("toAcpNotifications messageId", () => {
|
||||
"assistant",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
{ messageId },
|
||||
@@ -5348,6 +5806,7 @@ describe("toAcpNotifications messageId", () => {
|
||||
"user",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
{ messageId },
|
||||
@@ -5362,6 +5821,7 @@ describe("toAcpNotifications messageId", () => {
|
||||
"assistant",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
{ messageId },
|
||||
@@ -5373,7 +5833,15 @@ describe("toAcpNotifications messageId", () => {
|
||||
});
|
||||
|
||||
it("omits messageId when none is supplied", () => {
|
||||
const result = toAcpNotifications("hello", "assistant", "test", {}, {} as AcpClient, console);
|
||||
const result = toAcpNotifications(
|
||||
"hello",
|
||||
"assistant",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
);
|
||||
expect(result[0].update).not.toHaveProperty("messageId");
|
||||
});
|
||||
|
||||
@@ -5390,6 +5858,7 @@ describe("toAcpNotifications messageId", () => {
|
||||
"assistant",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
{ messageId, registerHooks: false },
|
||||
@@ -5406,6 +5875,7 @@ describe("toAcpNotifications thinking chunks", () => {
|
||||
"assistant",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
);
|
||||
@@ -5427,6 +5897,7 @@ describe("toAcpNotifications thinking chunks", () => {
|
||||
"assistant",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
);
|
||||
@@ -5440,6 +5911,7 @@ describe("toAcpNotifications thinking chunks", () => {
|
||||
"assistant",
|
||||
"test",
|
||||
{},
|
||||
new Map(),
|
||||
{} as AcpClient,
|
||||
console,
|
||||
);
|
||||
@@ -5597,6 +6069,7 @@ describe("agent selection config option", () => {
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
messageIdToUuid: new Map(),
|
||||
};
|
||||
return { session: agent.sessions[sessionId]!, applyFlagSettings };
|
||||
|
||||
@@ -124,6 +124,8 @@ describe("session config options", () => {
|
||||
),
|
||||
configOptions: structuredClone(MOCK_CONFIG_OPTIONS),
|
||||
contextWindowSize: 200000,
|
||||
toolUseCache: {},
|
||||
waitingForToolCall: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1112,6 +1114,12 @@ describe("session config options", () => {
|
||||
{ id: "dontAsk", name: "Don't Ask", description: "Deny if not pre-approved" },
|
||||
],
|
||||
};
|
||||
session.toolUseCache["toolu_1"] = {
|
||||
type: "tool_use",
|
||||
id: "toolu_1",
|
||||
name: "ExitPlanMode",
|
||||
input: { plan: "do stuff" },
|
||||
};
|
||||
|
||||
const canUseTool = (agent as any).canUseTool(SESSION_ID);
|
||||
const signal = new AbortController().signal;
|
||||
@@ -1144,6 +1152,12 @@ describe("session config options", () => {
|
||||
],
|
||||
};
|
||||
permissionResponse = { outcome: { outcome: "selected", optionId: "auto" } };
|
||||
session.toolUseCache["toolu_2"] = {
|
||||
type: "tool_use",
|
||||
id: "toolu_2",
|
||||
name: "ExitPlanMode",
|
||||
input: { plan: "do stuff" },
|
||||
};
|
||||
|
||||
const canUseTool = (agent as any).canUseTool(SESSION_ID);
|
||||
const result = await canUseTool(
|
||||
@@ -1171,6 +1185,12 @@ describe("session config options", () => {
|
||||
{ id: "dontAsk", name: "Don't Ask", description: "Deny if not pre-approved" },
|
||||
],
|
||||
};
|
||||
session.toolUseCache["toolu_3"] = {
|
||||
type: "tool_use",
|
||||
id: "toolu_3",
|
||||
name: "ExitPlanMode",
|
||||
input: { plan: "do stuff" },
|
||||
};
|
||||
|
||||
const canUseTool = (agent as any).canUseTool(SESSION_ID);
|
||||
const signal = new AbortController().signal;
|
||||
|
||||
@@ -10,7 +10,13 @@ import {
|
||||
BetaBashCodeExecutionResultBlock,
|
||||
BetaBashCodeExecutionToolResultBlockParam,
|
||||
} from "@anthropic-ai/sdk/resources/beta.mjs";
|
||||
import { AcpClient, toAcpNotifications, ToolUseCache, Logger } from "../acp-agent.js";
|
||||
import {
|
||||
AcpClient,
|
||||
toAcpNotifications,
|
||||
ToolUseCache,
|
||||
Logger,
|
||||
WaitingForToolCall,
|
||||
} from "../acp-agent.js";
|
||||
import {
|
||||
toolUpdateFromToolResult,
|
||||
createPostToolUseHook,
|
||||
@@ -37,6 +43,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { command: "echo hello" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const toolResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
@@ -50,6 +57,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -72,6 +80,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { file_path: "/test/file.txt" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
// ToolResultBlockParam content can be string or array of TextBlockParam
|
||||
const toolResult: ToolResultBlockParam = {
|
||||
@@ -86,6 +95,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -108,6 +118,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { query: "test" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
// BetaMCPToolResultBlock content can be string or Array<BetaTextBlock>
|
||||
const toolResult: BetaMCPToolResultBlock = {
|
||||
@@ -122,6 +133,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -144,6 +156,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { term: "test" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
// BetaTextBlock requires citations field
|
||||
const arrayContent: BetaTextBlock[] = [
|
||||
@@ -163,6 +176,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -185,6 +199,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { query: "test search" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
// BetaWebSearchResultBlock from SDK
|
||||
const searchResults: BetaWebSearchResultBlock[] = [
|
||||
@@ -208,6 +223,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -230,6 +246,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { command: "ls -la" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
// BetaBashCodeExecutionResultBlock from SDK
|
||||
const bashResult: BetaBashCodeExecutionResultBlock = {
|
||||
@@ -251,6 +268,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -273,6 +291,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { command: "invalid_command" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const toolResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
@@ -286,6 +305,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -308,6 +328,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { todos: [{ content: "Test task", status: "pending" }] },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const toolResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
@@ -321,6 +342,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -351,11 +373,14 @@ describe("rawOutput in tool call updates", () => {
|
||||
is_error: false,
|
||||
};
|
||||
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[toolResult],
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -383,6 +408,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
input: { file_path: "/test/image.png" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const imageBlock: ImageBlockParam = {
|
||||
type: "image",
|
||||
@@ -401,6 +427,7 @@ describe("rawOutput in tool call updates", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -779,6 +806,7 @@ describe("Bash terminal output", () => {
|
||||
// Reset before each test: toAcpNotifications prunes the cache entry once it
|
||||
// maps the tool_result, so a shared object would be empty by the 2nd test.
|
||||
let toolUseCache: ToolUseCache;
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
beforeEach(() => {
|
||||
toolUseCache = {
|
||||
toolu_bash: {
|
||||
@@ -814,6 +842,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ clientCapabilities },
|
||||
@@ -854,6 +883,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -880,6 +910,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ clientCapabilities },
|
||||
@@ -895,6 +926,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ clientCapabilities: { _meta: { terminal_output: true } } },
|
||||
@@ -915,6 +947,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCacheWithoutSupport,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -947,6 +980,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ clientCapabilities },
|
||||
@@ -969,6 +1003,7 @@ describe("Bash terminal output", () => {
|
||||
describe("toolUseCache pruning", () => {
|
||||
it("retains the tool_use entry until its result, then prunes it", () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const toolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_read",
|
||||
@@ -977,7 +1012,15 @@ describe("Bash terminal output", () => {
|
||||
};
|
||||
|
||||
// tool_use is cached and kept (the matching result hasn't arrived yet).
|
||||
toAcpNotifications([toolUse], "assistant", "s", toolUseCache, mockClient, mockLogger);
|
||||
toAcpNotifications(
|
||||
[toolUse],
|
||||
"assistant",
|
||||
"s",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
expect(toolUseCache.toolu_read).toBeDefined();
|
||||
|
||||
// tool_result resolves it, so the entry is pruned to bound memory.
|
||||
@@ -986,7 +1029,15 @@ describe("Bash terminal output", () => {
|
||||
tool_use_id: "toolu_read",
|
||||
content: [{ type: "text", text: "hello" }],
|
||||
};
|
||||
toAcpNotifications([toolResult], "assistant", "s", toolUseCache, mockClient, mockLogger);
|
||||
toAcpNotifications(
|
||||
[toolResult],
|
||||
"assistant",
|
||||
"s",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
expect(toolUseCache.toolu_read).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -994,6 +1045,7 @@ describe("Bash terminal output", () => {
|
||||
describe("post-tool-use hook sends diff content for Edit tool", () => {
|
||||
it("should include content and locations from structuredPatch in hook update", async () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const hookUpdates: any[] = [];
|
||||
const mockClientWithUpdate = {
|
||||
@@ -1019,6 +1071,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1073,6 +1126,7 @@ describe("Bash terminal output", () => {
|
||||
|
||||
it("should include multiple diff blocks for replaceAll with multiple hunks", async () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const hookUpdates: any[] = [];
|
||||
const mockClientWithUpdate = {
|
||||
@@ -1098,6 +1152,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1158,6 +1213,7 @@ describe("Bash terminal output", () => {
|
||||
|
||||
it("should not include content/locations for non-Edit tools", async () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const hookUpdates: any[] = [];
|
||||
const mockClientWithUpdate = {
|
||||
@@ -1178,6 +1234,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1213,6 +1270,7 @@ describe("Bash terminal output", () => {
|
||||
// tool_use time and was never corrected after the tool ran.
|
||||
it("should emit a real diff for Write of an existing file (type: update)", async () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const hookUpdates: any[] = [];
|
||||
const mockClientWithUpdate = {
|
||||
sessionUpdate: async (notification: any) => {
|
||||
@@ -1235,6 +1293,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1289,6 +1348,7 @@ describe("Bash terminal output", () => {
|
||||
|
||||
it("should still emit a sensible diff for Write of a brand-new file (type: create)", async () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const hookUpdates: any[] = [];
|
||||
const mockClientWithUpdate = {
|
||||
sessionUpdate: async (notification: any) => {
|
||||
@@ -1311,6 +1371,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1368,6 +1429,7 @@ describe("Bash terminal output", () => {
|
||||
};
|
||||
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
// Capture session updates sent by the hook callback
|
||||
const hookUpdates: any[] = [];
|
||||
@@ -1389,6 +1451,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
{ clientCapabilities },
|
||||
@@ -1418,6 +1481,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
{ clientCapabilities },
|
||||
@@ -1469,6 +1533,7 @@ describe("Bash terminal output", () => {
|
||||
|
||||
it("should not include terminal _meta in hook update when client lacks terminal_output support", async () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const hookUpdates: any[] = [];
|
||||
const mockClientWithUpdate = {
|
||||
@@ -1490,6 +1555,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
// No clientCapabilities — terminal_output not supported
|
||||
@@ -1514,6 +1580,7 @@ describe("Bash terminal output", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClientWithUpdate,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1645,6 +1712,7 @@ describe("toAcpNotifications - TodoWrite with undefined input regression", () =>
|
||||
|
||||
it("should not throw when TodoWrite tool_use has undefined input", () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[
|
||||
@@ -1658,6 +1726,7 @@ describe("toAcpNotifications - TodoWrite with undefined input regression", () =>
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1669,6 +1738,7 @@ describe("toAcpNotifications - TodoWrite with undefined input regression", () =>
|
||||
|
||||
it("should still emit plan update when TodoWrite has valid input", () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[
|
||||
@@ -1682,6 +1752,7 @@ describe("toAcpNotifications - TodoWrite with undefined input regression", () =>
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
@@ -1780,6 +1851,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
|
||||
it("suppresses tool_call for TaskCreate/TaskUpdate/TaskList/TaskGet on tool_use", () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const taskState: TaskState = new Map();
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
@@ -1797,6 +1869,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
@@ -1808,6 +1881,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
|
||||
it("emits a plan snapshot after a TaskCreate tool_result and accumulates state", () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const taskState: TaskState = new Map();
|
||||
|
||||
toAcpNotifications(
|
||||
@@ -1822,6 +1896,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
@@ -1839,6 +1914,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
@@ -1863,6 +1939,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"assistant",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
@@ -1880,6 +1957,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
@@ -1896,6 +1974,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
|
||||
it("emits a plan snapshot reflecting status changes after a TaskUpdate tool_result", () => {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const taskState: TaskState = new Map([["1", { subject: "First", status: "pending" as const }]]);
|
||||
toolUseCache["update-1"] = {
|
||||
type: "tool_use",
|
||||
@@ -1920,6 +1999,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
@@ -1937,6 +2017,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"list-1": { type: "tool_use", id: "list-1", name: "TaskList", input: {} },
|
||||
"get-1": { type: "tool_use", id: "get-1", name: "TaskGet", input: { taskId: "1" } },
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const taskState: TaskState = new Map([
|
||||
["1", { subject: "Existing", status: "in_progress" as const }],
|
||||
]);
|
||||
@@ -1949,6 +2030,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
@@ -1967,6 +2049,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
input: { subject: "A", description: "" },
|
||||
},
|
||||
};
|
||||
const waitingForToolCall: WaitingForToolCall = new Map();
|
||||
const taskState: TaskState = new Map();
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
@@ -1981,6 +2064,7 @@ describe("toAcpNotifications - Task* tools", () => {
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
waitingForToolCall,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ taskState },
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2021"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
|
||||
Reference in New Issue
Block a user