mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 12:40:32 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca1fe79e6f | ||
|
|
079614ab05 | ||
|
|
2ecfa83b26 | ||
|
|
dd67450fd9 | ||
|
|
d6a069f9fe | ||
|
|
85cd70c9f3 | ||
|
|
860c0441c7 | ||
|
|
3c98d76197 | ||
|
|
07a0fbc2f6 | ||
|
|
a0da4277e3 |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.27.0"
|
||||
".": "0.29.1"
|
||||
}
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,5 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## [0.29.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.29.0...v0.29.1) (2026-04-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* emit usage updates from Claude stream events ([#506](https://github.com/agentclientprotocol/claude-agent-acp/issues/506)) ([dd67450](https://github.com/agentclientprotocol/claude-agent-acp/commit/dd67450fd9bddc82db3e71273b535e07b2672804))
|
||||
* Remove dot from auto mode description ([#561](https://github.com/agentclientprotocol/claude-agent-acp/issues/561)) ([2ecfa83](https://github.com/agentclientprotocol/claude-agent-acp/commit/2ecfa83b26db58deaded210463fa6ff21d0dff70))
|
||||
* Update to claude-agent-sdk 0.2.112 to fix Auto bug with Opus 4.7 ([#562](https://github.com/agentclientprotocol/claude-agent-acp/issues/562)) ([079614a](https://github.com/agentclientprotocol/claude-agent-acp/commit/079614ab05afba17e2cce0d3d238df7b90e17389))
|
||||
|
||||
## [0.29.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.28.0...v0.29.0) (2026-04-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to claude-agent-sdk 0.2.111 (Opus 4.7) ([#557](https://github.com/agentclientprotocol/claude-agent-acp/issues/557)) ([85cd70c](https://github.com/agentclientprotocol/claude-agent-acp/commit/85cd70c9f3be47c9c404958547c4046d866db1c9))
|
||||
|
||||
## [0.28.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.27.0...v0.28.0) (2026-04-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to claude-agent-sdk 0.2.109 ([#549](https://github.com/agentclientprotocol/claude-agent-acp/issues/549)) ([07a0fbc](https://github.com/agentclientprotocol/claude-agent-acp/commit/07a0fbc2f6bc388541d064a436412bdd850772cb))
|
||||
|
||||
## [0.27.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.26.0...v0.27.0) (2026-04-13)
|
||||
|
||||
|
||||
|
||||
180
package-lock.json
generated
180
package-lock.json
generated
@@ -1,49 +1,49 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.27.0",
|
||||
"version": "0.29.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.27.0",
|
||||
"version": "0.29.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.18.2",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.104",
|
||||
"@agentclientprotocol/sdk": "0.19.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.112",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.88.0",
|
||||
"@anthropic-ai/sdk": "0.89.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@types/node": "25.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.58.1",
|
||||
"@typescript-eslint/parser": "8.58.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.58.2",
|
||||
"@typescript-eslint/parser": "8.58.2",
|
||||
"eslint": "10.2.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.5.0",
|
||||
"prettier": "3.8.2",
|
||||
"prettier": "3.8.3",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "0.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.18.2.tgz",
|
||||
"integrity": "sha512-l/o9NKvUc00GPa6RFJ4AccQq2O/PAf83xQ75mThHuL3H571iN4+PEdwnTBez67sS8Nv2aSA373xCZ5CbTXEwzA==",
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.19.0.tgz",
|
||||
"integrity": "sha512-U9I8ws9WTOk6jCBAWpXefGSDgVXn14/kV6HFzwWGcstQ02mOQgClMAROHmoIn9GqZbDBDEOkdIbP4P4TEMQdug==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.2.104",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.104.tgz",
|
||||
"integrity": "sha512-lVm+nS79r6WWlDnv5AgRzTtAlbP8O6M6kkWmDZAWE3nt9agmngxls9frJFvH55uzws2+6l0yyup/JYspfijkzw==",
|
||||
"version": "0.2.112",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.112.tgz",
|
||||
"integrity": "sha512-vMFoiDKlOive8p3tphpV1gQaaytOipwGJ+uw9mvvaLQUODSC2+fCdRDAY25i2Tsv+lOtxzXBKctmaDuWqZY7ig==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.81.0",
|
||||
@@ -88,9 +88,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.88.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.88.0.tgz",
|
||||
"integrity": "sha512-QQOtB5U9ZBJQj6y1ICmDZl14LWa4JCiJRoihI+0yuZ4OjbONrakP0yLwPv4DJFb3VYCtQM31bTOpCBMs2zghPw==",
|
||||
"version": "0.89.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.89.0.tgz",
|
||||
"integrity": "sha512-nyGau0zex62EpU91hsHa0zod973YEoiMgzWZ9hC55WdiOLrE4AGpcg4wXI7lFqtvMLqMcLfewQU9sHgQB6psow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -746,9 +746,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
|
||||
"integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -1152,17 +1152,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz",
|
||||
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz",
|
||||
"integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.58.1",
|
||||
"@typescript-eslint/type-utils": "8.58.1",
|
||||
"@typescript-eslint/utils": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
||||
"@typescript-eslint/scope-manager": "8.58.2",
|
||||
"@typescript-eslint/type-utils": "8.58.2",
|
||||
"@typescript-eslint/utils": "8.58.2",
|
||||
"@typescript-eslint/visitor-keys": "8.58.2",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -1175,22 +1175,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.58.1",
|
||||
"@typescript-eslint/parser": "^8.58.2",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz",
|
||||
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz",
|
||||
"integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/typescript-estree": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
||||
"@typescript-eslint/scope-manager": "8.58.2",
|
||||
"@typescript-eslint/types": "8.58.2",
|
||||
"@typescript-eslint/typescript-estree": "8.58.2",
|
||||
"@typescript-eslint/visitor-keys": "8.58.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1206,14 +1206,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz",
|
||||
"integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz",
|
||||
"integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.58.1",
|
||||
"@typescript-eslint/types": "^8.58.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.58.2",
|
||||
"@typescript-eslint/types": "^8.58.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1228,14 +1228,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz",
|
||||
"integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz",
|
||||
"integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1"
|
||||
"@typescript-eslint/types": "8.58.2",
|
||||
"@typescript-eslint/visitor-keys": "8.58.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1246,9 +1246,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz",
|
||||
"integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz",
|
||||
"integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1263,15 +1263,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz",
|
||||
"integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz",
|
||||
"integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/typescript-estree": "8.58.1",
|
||||
"@typescript-eslint/utils": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.2",
|
||||
"@typescript-eslint/typescript-estree": "8.58.2",
|
||||
"@typescript-eslint/utils": "8.58.2",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -1288,9 +1288,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz",
|
||||
"integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz",
|
||||
"integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1302,16 +1302,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz",
|
||||
"integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz",
|
||||
"integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.58.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
||||
"@typescript-eslint/project-service": "8.58.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.58.2",
|
||||
"@typescript-eslint/types": "8.58.2",
|
||||
"@typescript-eslint/visitor-keys": "8.58.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1330,16 +1330,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz",
|
||||
"integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz",
|
||||
"integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/typescript-estree": "8.58.1"
|
||||
"@typescript-eslint/scope-manager": "8.58.2",
|
||||
"@typescript-eslint/types": "8.58.2",
|
||||
"@typescript-eslint/typescript-estree": "8.58.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1354,13 +1354,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz",
|
||||
"integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==",
|
||||
"version": "8.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz",
|
||||
"integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2521,9 +2521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.12",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
|
||||
"integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==",
|
||||
"version": "4.12.14",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
|
||||
"integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
@@ -3312,9 +3312,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.9",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
||||
"integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
||||
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3351,9 +3351,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz",
|
||||
"integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==",
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
|
||||
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -3684,9 +3684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
|
||||
"integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
|
||||
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -3842,9 +3842,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz",
|
||||
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
16
package.json
16
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.27.0",
|
||||
"version": "0.29.1",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -60,22 +60,22 @@
|
||||
"author": "Zed Industries",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.18.2",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.104",
|
||||
"@agentclientprotocol/sdk": "0.19.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.112",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.88.0",
|
||||
"@anthropic-ai/sdk": "0.89.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@types/node": "25.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.58.1",
|
||||
"@typescript-eslint/parser": "8.58.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.58.2",
|
||||
"@typescript-eslint/parser": "8.58.2",
|
||||
"eslint": "10.2.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.5.0",
|
||||
"prettier": "3.8.2",
|
||||
"prettier": "3.8.3",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
182
src/acp-agent.ts
182
src/acp-agent.ts
@@ -54,7 +54,6 @@ import {
|
||||
Query,
|
||||
query,
|
||||
SDKPartialAssistantMessage,
|
||||
SDKResultMessage,
|
||||
SDKUserMessage,
|
||||
SlashCommand,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
@@ -108,6 +107,22 @@ type AccumulatedUsage = {
|
||||
cachedWriteTokens: number;
|
||||
};
|
||||
|
||||
type UsageSnapshot = {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
cache_read_input_tokens: number;
|
||||
cache_creation_input_tokens: number;
|
||||
};
|
||||
|
||||
const ZERO_USAGE = Object.freeze({
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
});
|
||||
|
||||
const DEFAULT_CONTEXT_WINDOW = 200000;
|
||||
|
||||
type Session = {
|
||||
query: Query;
|
||||
input: Pushable<SDKUserMessage>;
|
||||
@@ -126,6 +141,12 @@ type Session = {
|
||||
nextPendingOrder: number;
|
||||
abortController: AbortController;
|
||||
emitRawSDKMessages: boolean | SDKMessageFilter[];
|
||||
/** Context window size of the last top-level assistant model, carried across
|
||||
* prompts so mid-stream usage_update notifications report a correct `size`
|
||||
* before the turn's first result message arrives. Defaults to
|
||||
* DEFAULT_CONTEXT_WINDOW, refreshed from each result's modelUsage, and
|
||||
* invalidated when the user switches the session's model. */
|
||||
contextWindowSize: number;
|
||||
};
|
||||
|
||||
/** Compute a stable fingerprint of the session-defining params so we can
|
||||
@@ -545,8 +566,8 @@ export class ClaudeAcpAgent implements Agent {
|
||||
};
|
||||
|
||||
let lastAssistantTotalUsage: number | null = null;
|
||||
let lastAssistantUsage: UsageSnapshot | null = null;
|
||||
let lastAssistantModel: string | null = null;
|
||||
let lastContextWindowSize: number = 200000;
|
||||
|
||||
const userMessage = promptToClaude(params);
|
||||
|
||||
@@ -628,12 +649,13 @@ export class ClaudeAcpAgent implements Agent {
|
||||
// "944k/1m" right after the user sees "Compacting completed",
|
||||
// which is confusing and wrong.
|
||||
lastAssistantTotalUsage = 0;
|
||||
lastAssistantUsage = null;
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: message.session_id,
|
||||
update: {
|
||||
sessionUpdate: "usage_update",
|
||||
used: 0,
|
||||
size: lastContextWindowSize,
|
||||
size: session.contextWindowSize,
|
||||
},
|
||||
});
|
||||
await this.client.sessionUpdate({
|
||||
@@ -670,6 +692,9 @@ export class ClaudeAcpAgent implements Agent {
|
||||
case "task_progress":
|
||||
case "task_updated":
|
||||
case "elicitation_complete":
|
||||
case "plugin_install":
|
||||
case "memory_recall":
|
||||
case "notification":
|
||||
case "api_retry":
|
||||
// Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
|
||||
break;
|
||||
@@ -688,8 +713,13 @@ export class ClaudeAcpAgent implements Agent {
|
||||
const matchingModelUsage = lastAssistantModel
|
||||
? getMatchingModelUsage(message.modelUsage, lastAssistantModel)
|
||||
: null;
|
||||
const contextWindowSize = matchingModelUsage?.contextWindow ?? 200000;
|
||||
lastContextWindowSize = contextWindowSize;
|
||||
// Only overwrite when we have an authoritative value — a miss
|
||||
// (e.g. a turn with no top-level assistant message) would
|
||||
// otherwise discard the window learned on a prior turn and
|
||||
// leave the next prompt's mid-stream updates reporting 200k.
|
||||
if (matchingModelUsage) {
|
||||
session.contextWindowSize = matchingModelUsage.contextWindow;
|
||||
}
|
||||
|
||||
// Send usage_update notification
|
||||
if (lastAssistantTotalUsage !== null) {
|
||||
@@ -698,7 +728,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
update: {
|
||||
sessionUpdate: "usage_update",
|
||||
used: lastAssistantTotalUsage,
|
||||
size: contextWindowSize,
|
||||
size: session.contextWindowSize,
|
||||
cost: {
|
||||
amount: message.total_cost_usd,
|
||||
currency: "USD",
|
||||
@@ -772,6 +802,57 @@ export class ClaudeAcpAgent implements Agent {
|
||||
break;
|
||||
}
|
||||
case "stream_event": {
|
||||
if (
|
||||
message.parent_tool_use_id === null &&
|
||||
(message.event.type === "message_start" || message.event.type === "message_delta")
|
||||
) {
|
||||
if (message.event.type === "message_start") {
|
||||
lastAssistantUsage = snapshotFromUsage(message.event.message.usage);
|
||||
const model = message.event.message.model;
|
||||
if (model && model !== "<synthetic>") {
|
||||
lastAssistantModel = model;
|
||||
// Only upgrade from the default — once a `result` has given
|
||||
// us an authoritative window, trust it over the heuristic.
|
||||
// Model switches invalidate the cached window via
|
||||
// `syncSessionConfigState`, which resets us back to the
|
||||
// default so this branch runs again for the new model.
|
||||
if (session.contextWindowSize === DEFAULT_CONTEXT_WINDOW) {
|
||||
const inferred = inferContextWindowFromModel(model);
|
||||
if (inferred !== null) {
|
||||
session.contextWindowSize = inferred;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const usage = message.event.usage;
|
||||
const prev: Readonly<UsageSnapshot> = lastAssistantUsage ?? ZERO_USAGE;
|
||||
// Per Anthropic API, message_delta usage fields are *cumulative*;
|
||||
// nullable fields (input_tokens and the cache fields) fall back
|
||||
// to the prior snapshot when the server omits them from this
|
||||
// delta. Only output_tokens is guaranteed non-null.
|
||||
lastAssistantUsage = {
|
||||
input_tokens: usage.input_tokens ?? prev.input_tokens,
|
||||
output_tokens: usage.output_tokens,
|
||||
cache_read_input_tokens:
|
||||
usage.cache_read_input_tokens ?? prev.cache_read_input_tokens,
|
||||
cache_creation_input_tokens:
|
||||
usage.cache_creation_input_tokens ?? prev.cache_creation_input_tokens,
|
||||
};
|
||||
}
|
||||
|
||||
const nextUsage = totalTokens(lastAssistantUsage);
|
||||
if (nextUsage !== lastAssistantTotalUsage) {
|
||||
lastAssistantTotalUsage = nextUsage;
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: params.sessionId,
|
||||
update: {
|
||||
sessionUpdate: "usage_update",
|
||||
used: nextUsage,
|
||||
size: session.contextWindowSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const notification of streamEventToAcpNotifications(
|
||||
message,
|
||||
params.sessionId,
|
||||
@@ -814,29 +895,16 @@ export class ClaudeAcpAgent implements Agent {
|
||||
}
|
||||
}
|
||||
|
||||
// Store latest assistant usage (excluding subagents)
|
||||
// Sum all token types as a proxy for post-turn context occupancy:
|
||||
// current turn's output will become next turn's input.
|
||||
// Note: per the Anthropic API, input_tokens excludes cache tokens —
|
||||
// cache_read and cache_creation are reported separately, so summing
|
||||
// all four fields is not double-counting.
|
||||
if ((message.message as any).usage && message.parent_tool_use_id === null) {
|
||||
const messageWithUsage = message.message as unknown as SDKResultMessage;
|
||||
lastAssistantTotalUsage =
|
||||
messageWithUsage.usage.input_tokens +
|
||||
messageWithUsage.usage.output_tokens +
|
||||
messageWithUsage.usage.cache_read_input_tokens +
|
||||
messageWithUsage.usage.cache_creation_input_tokens;
|
||||
}
|
||||
// Track the current top-level model for context window size lookup
|
||||
// (exclude subagent messages to stay in sync with lastAssistantTotalUsage)
|
||||
if (
|
||||
message.type === "assistant" &&
|
||||
message.parent_tool_use_id === null &&
|
||||
message.message.model &&
|
||||
message.message.model !== "<synthetic>"
|
||||
) {
|
||||
lastAssistantModel = message.message.model;
|
||||
// Snapshot the latest top-level assistant usage and model so the
|
||||
// next `result` can emit a usage_update tied to the right context
|
||||
// window. Subagent messages are excluded to keep the snapshot
|
||||
// aligned with what the user's current selection is producing.
|
||||
if (message.type === "assistant" && message.parent_tool_use_id === null) {
|
||||
lastAssistantUsage = snapshotFromUsage(message.message.usage);
|
||||
lastAssistantTotalUsage = totalTokens(lastAssistantUsage);
|
||||
if (message.message.model && message.message.model !== "<synthetic>") {
|
||||
lastAssistantModel = message.message.model;
|
||||
}
|
||||
}
|
||||
|
||||
// Slash commands like /compact can generate invalid output... doesn't match
|
||||
@@ -1331,6 +1399,13 @@ export class ClaudeAcpAgent implements Agent {
|
||||
if (configId === "mode") {
|
||||
session.modes = { ...session.modes, currentModeId: value };
|
||||
} else if (configId === "model") {
|
||||
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.models = { ...session.models, currentModelId: value };
|
||||
}
|
||||
}
|
||||
@@ -1554,7 +1629,8 @@ export class ClaudeAcpAgent implements Agent {
|
||||
if (
|
||||
creationOpts.resume &&
|
||||
error instanceof Error &&
|
||||
error.message === "Query closed before response received"
|
||||
(error.message === "Query closed before response received" ||
|
||||
error.message.includes("No conversation found with session ID"))
|
||||
) {
|
||||
throw RequestError.resourceNotFound(sessionId);
|
||||
}
|
||||
@@ -1578,7 +1654,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
{
|
||||
id: "auto",
|
||||
name: "Auto",
|
||||
description: "Use a model classifier to approve/deny permission prompts.",
|
||||
description: "Use a model classifier to approve/deny permission prompts",
|
||||
},
|
||||
{
|
||||
id: "default",
|
||||
@@ -1638,6 +1714,8 @@ export class ClaudeAcpAgent implements Agent {
|
||||
nextPendingOrder: 0,
|
||||
abortController,
|
||||
emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false,
|
||||
contextWindowSize:
|
||||
inferContextWindowFromModel(models.currentModelId) ?? DEFAULT_CONTEXT_WINDOW,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -1674,6 +1752,38 @@ function sessionUsage(session: Session) {
|
||||
};
|
||||
}
|
||||
|
||||
/** Sum all four fields as a proxy for post-turn context occupancy: the current
|
||||
* turn's output becomes next turn's input. Per the Anthropic API, input_tokens
|
||||
* excludes cache tokens — cache_read and cache_creation are reported
|
||||
* separately — so summing all four is not double-counting. */
|
||||
function totalTokens(usage: UsageSnapshot): number {
|
||||
return (
|
||||
usage.input_tokens +
|
||||
usage.output_tokens +
|
||||
usage.cache_read_input_tokens +
|
||||
usage.cache_creation_input_tokens
|
||||
);
|
||||
}
|
||||
|
||||
/** Project a nullable API usage object into our non-null snapshot shape.
|
||||
* Both SDK message_start and assistant message `usage` have `number | null`
|
||||
* cache fields; we coerce absent values to 0 so `totalTokens` never hits
|
||||
* NaN. Delta events have different semantics (cumulative + prev fallback)
|
||||
* and are handled inline. */
|
||||
function snapshotFromUsage(usage: {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
cache_read_input_tokens: number | null;
|
||||
cache_creation_input_tokens: number | null;
|
||||
}): UsageSnapshot {
|
||||
return {
|
||||
input_tokens: usage.input_tokens,
|
||||
output_tokens: usage.output_tokens,
|
||||
cache_read_input_tokens: usage.cache_read_input_tokens ?? 0,
|
||||
cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
function createEnvForGateway(gatewayMeta?: GatewayAuthMeta) {
|
||||
if (!gatewayMeta) {
|
||||
return {};
|
||||
@@ -2298,6 +2408,16 @@ function commonPrefixLength(a: string, b: string) {
|
||||
return i;
|
||||
}
|
||||
|
||||
/** Best-effort first guess of a model's context window from its ID, used only
|
||||
* until a `result` message arrives with the authoritative `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;
|
||||
return null;
|
||||
}
|
||||
|
||||
function getMatchingModelUsage(modelUsage: Record<string, ModelUsage>, currentModel: string) {
|
||||
let bestKey: string | null = null;
|
||||
let bestLen = 0;
|
||||
|
||||
@@ -1350,6 +1350,7 @@ describe("stop reason propagation", () => {
|
||||
nextPendingOrder: 0,
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1491,6 +1492,7 @@ describe("stop reason propagation", () => {
|
||||
pendingMessages: new Map(),
|
||||
nextPendingOrder: 0,
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
};
|
||||
|
||||
const response = await agent.prompt({
|
||||
@@ -1566,6 +1568,7 @@ describe("session/close", () => {
|
||||
nextPendingOrder: 0,
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
}
|
||||
@@ -1660,6 +1663,7 @@ describe("getOrCreateSession param change detection", () => {
|
||||
nextPendingOrder: 0,
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
}
|
||||
@@ -1819,6 +1823,23 @@ describe("usage_update computation", () => {
|
||||
};
|
||||
}
|
||||
|
||||
function createStreamEvent(
|
||||
eventType: "message_start" | "message_delta",
|
||||
payload: Record<string, unknown>,
|
||||
parentToolUseId: string | null = null,
|
||||
) {
|
||||
return {
|
||||
type: "stream_event" as const,
|
||||
parent_tool_use_id: parentToolUseId,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
event:
|
||||
eventType === "message_start"
|
||||
? { type: "message_start" as const, message: payload }
|
||||
: { type: "message_delta" as const, ...payload },
|
||||
};
|
||||
}
|
||||
|
||||
function createMockAgentWithCapture() {
|
||||
const updates: any[] = [];
|
||||
const mockClient = {
|
||||
@@ -1875,6 +1896,7 @@ describe("usage_update computation", () => {
|
||||
nextPendingOrder: 0,
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1915,6 +1937,461 @@ describe("usage_update computation", () => {
|
||||
expect(usageUpdate.update.used).toBe(1800);
|
||||
});
|
||||
|
||||
it("stream_event message_start emits usage_update before result", async () => {
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-opus-4-20250514",
|
||||
usage: {
|
||||
input_tokens: 1000,
|
||||
output_tokens: 500,
|
||||
cache_read_input_tokens: 200,
|
||||
cache_creation_input_tokens: 100,
|
||||
},
|
||||
}),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-opus-4-20250514": {
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
cacheReadInputTokens: 200,
|
||||
cacheCreationInputTokens: 100,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.01,
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(2);
|
||||
expect(usageUpdates[0].update.used).toBe(1800);
|
||||
// First prompt of a session has no prior result to learn the window from,
|
||||
// so the mid-stream update falls back to the default context window.
|
||||
expect(usageUpdates[0].update.size).toBe(200000);
|
||||
expect(usageUpdates[0].update.cost).toBeUndefined();
|
||||
expect(usageUpdates[1].update.used).toBe(1800);
|
||||
expect(usageUpdates[1].update.size).toBe(1000000);
|
||||
expect(usageUpdates[1].update.cost).toBeDefined();
|
||||
});
|
||||
|
||||
it("stream_event message_delta patches previous snapshot", async () => {
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-opus-4-20250514",
|
||||
usage: {
|
||||
input_tokens: 1000,
|
||||
output_tokens: 0,
|
||||
cache_read_input_tokens: 200,
|
||||
cache_creation_input_tokens: 100,
|
||||
},
|
||||
}),
|
||||
createStreamEvent("message_delta", {
|
||||
usage: { output_tokens: 500 },
|
||||
}),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-opus-4-20250514": {
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
cacheReadInputTokens: 200,
|
||||
cacheCreationInputTokens: 100,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.01,
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(3);
|
||||
expect(usageUpdates[0].update.used).toBe(1300);
|
||||
expect(usageUpdates[0].update.cost).toBeUndefined();
|
||||
expect(usageUpdates[1].update.used).toBe(1800);
|
||||
expect(usageUpdates[1].update.cost).toBeUndefined();
|
||||
expect(usageUpdates[2].update.used).toBe(1800);
|
||||
expect(usageUpdates[2].update.cost).toBeDefined();
|
||||
});
|
||||
|
||||
it("mid-stream size is inferred from a 1M model name before the first result", async () => {
|
||||
// On the very first prompt there is no learned context window yet, so the
|
||||
// mid-stream update would otherwise fall back to 200k. A "-1m" suffix in
|
||||
// the SDK model ID is enough signal to emit 1_000_000 up front.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-opus-4-6-1m",
|
||||
usage: {
|
||||
input_tokens: 2000,
|
||||
output_tokens: 1000,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
}),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-opus-4-6-1m": {
|
||||
inputTokens: 2000,
|
||||
outputTokens: 1000,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.02,
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(2);
|
||||
expect(usageUpdates[0].update.size).toBe(1000000);
|
||||
expect(usageUpdates[1].update.size).toBe(1000000);
|
||||
});
|
||||
|
||||
it("duplicate stream_event totals do not re-emit usage_update", async () => {
|
||||
// A message_delta whose cumulative totals match the prior snapshot should
|
||||
// not trigger a duplicate usage_update — only the result adds cost on top.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-opus-4-20250514",
|
||||
usage: {
|
||||
input_tokens: 1000,
|
||||
output_tokens: 500,
|
||||
cache_read_input_tokens: 200,
|
||||
cache_creation_input_tokens: 100,
|
||||
},
|
||||
}),
|
||||
createStreamEvent("message_delta", {
|
||||
usage: { output_tokens: 500 },
|
||||
}),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-opus-4-20250514": {
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
cacheReadInputTokens: 200,
|
||||
cacheCreationInputTokens: 100,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.01,
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(2);
|
||||
expect(usageUpdates[0].update.used).toBe(1800);
|
||||
expect(usageUpdates[0].update.cost).toBeUndefined();
|
||||
expect(usageUpdates[1].update.used).toBe(1800);
|
||||
expect(usageUpdates[1].update.cost).toBeDefined();
|
||||
});
|
||||
|
||||
it("mid-stream size uses the session's learned context window", async () => {
|
||||
// Session state persists the model's context window across prompts, so a
|
||||
// mid-stream update in a later prompt reports the real size immediately
|
||||
// instead of snapping back to the 200k default before the result arrives.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-opus-4-20250514",
|
||||
usage: {
|
||||
input_tokens: 2000,
|
||||
output_tokens: 1000,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
}),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-opus-4-20250514": {
|
||||
inputTokens: 2000,
|
||||
outputTokens: 1000,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.02,
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
// Simulate a prior prompt having learned the 1M window for this model.
|
||||
agent.sessions["test-session"].contextWindowSize = 1000000;
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(2);
|
||||
expect(usageUpdates[0].update.size).toBe(1000000);
|
||||
expect(usageUpdates[1].update.size).toBe(1000000);
|
||||
});
|
||||
|
||||
it("switching to a 1M model seeds the context window from the heuristic", async () => {
|
||||
// The heuristic runs at config-change time so mid-stream updates in the
|
||||
// next prompt already report 1M — without waiting for message_start or
|
||||
// the next `result` to correct us.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-opus-4-6-1m",
|
||||
usage: {
|
||||
input_tokens: 2000,
|
||||
output_tokens: 1000,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
}),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-opus-4-6-1m": {
|
||||
inputTokens: 2000,
|
||||
outputTokens: 1000,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.02,
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
const session = agent.sessions["test-session"];
|
||||
expect(session.contextWindowSize).toBe(200000);
|
||||
|
||||
(agent as any).syncSessionConfigState(session, "model", "claude-opus-4-6-1m");
|
||||
expect(session.contextWindowSize).toBe(1000000);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(2);
|
||||
expect(usageUpdates[0].update.size).toBe(1000000);
|
||||
expect(usageUpdates[1].update.size).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
|
||||
// not clobber the window learned on a prior turn — otherwise the next
|
||||
// prompt's mid-stream updates regress to the 200k default.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-haiku-4-5-20251001": {
|
||||
inputTokens: 100,
|
||||
outputTokens: 50,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.001,
|
||||
contextWindow: 200000,
|
||||
maxOutputTokens: 8192,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
const session = agent.sessions["test-session"];
|
||||
session.contextWindowSize = 1000000;
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
expect(session.contextWindowSize).toBe(1000000);
|
||||
// The emit itself falls back to session.contextWindowSize, which is
|
||||
// unchanged from the learned value.
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
// No lastAssistantTotalUsage was set (no top-level assistant / stream
|
||||
// event), so the result branch skips its emit entirely.
|
||||
expect(usageUpdates).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("switching the session's model invalidates the learned context window", async () => {
|
||||
// When the user switches models mid-session, the window learned for the
|
||||
// previous model would otherwise persist into the next prompt's first
|
||||
// mid-stream update. syncSessionConfigState should reset it so the next
|
||||
// turn's first update falls back to the heuristic (here: 200k default).
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-sonnet-4-6",
|
||||
usage: {
|
||||
input_tokens: 1000,
|
||||
output_tokens: 500,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
}),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-sonnet-4-6": {
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.01,
|
||||
contextWindow: 200000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
const session = agent.sessions["test-session"];
|
||||
session.contextWindowSize = 1000000;
|
||||
session.models = { ...session.models, currentModelId: "claude-opus-4-6-1m" };
|
||||
|
||||
// User flips the selector to a 200k model.
|
||||
(agent as any).syncSessionConfigState(session, "model", "claude-sonnet-4-6");
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(2);
|
||||
expect(usageUpdates[0].update.size).toBe(200000);
|
||||
expect(usageUpdates[1].update.size).toBe(200000);
|
||||
});
|
||||
|
||||
it("non-usage stream events do not re-emit usage_update", async () => {
|
||||
// content_block_* and message_stop carry no usage fields; they must not
|
||||
// trigger duplicate emits between the real message_start / message_delta
|
||||
// / result updates.
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent("message_start", {
|
||||
model: "claude-opus-4-20250514",
|
||||
usage: {
|
||||
input_tokens: 1000,
|
||||
output_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
}),
|
||||
{
|
||||
type: "stream_event" as const,
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
event: { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } },
|
||||
},
|
||||
{
|
||||
type: "stream_event" as const,
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
event: { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "hi" } },
|
||||
},
|
||||
{
|
||||
type: "stream_event" as const,
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
event: { type: "content_block_stop", index: 0 },
|
||||
},
|
||||
createStreamEvent("message_delta", {
|
||||
usage: { output_tokens: 200 },
|
||||
}),
|
||||
{
|
||||
type: "stream_event" as const,
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
event: { type: "message_stop" },
|
||||
},
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-opus-4-20250514": {
|
||||
inputTokens: 1000,
|
||||
outputTokens: 200,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.01,
|
||||
contextWindow: 1000000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
// Exactly three: message_start (1000), message_delta (1200), result (1200 + cost).
|
||||
expect(usageUpdates).toHaveLength(3);
|
||||
expect(usageUpdates[0].update.used).toBe(1000);
|
||||
expect(usageUpdates[1].update.used).toBe(1200);
|
||||
expect(usageUpdates[2].update.used).toBe(1200);
|
||||
expect(usageUpdates[2].update.cost).toBeDefined();
|
||||
});
|
||||
|
||||
it("subagent stream_event does not emit usage_update", async () => {
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
createStreamEvent(
|
||||
"message_start",
|
||||
{
|
||||
model: "claude-haiku-4-5-20251001",
|
||||
usage: {
|
||||
input_tokens: 500,
|
||||
output_tokens: 100,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
},
|
||||
"tool_use_123",
|
||||
),
|
||||
createResultMessageWithModel({
|
||||
modelUsage: {
|
||||
"claude-haiku-4-5-20251001": {
|
||||
inputTokens: 500,
|
||||
outputTokens: 100,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD: 0.001,
|
||||
contextWindow: 200000,
|
||||
maxOutputTokens: 8192,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
|
||||
|
||||
const usageUpdates = updates.filter((u: any) => u.update?.sessionUpdate === "usage_update");
|
||||
expect(usageUpdates).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("size reflects the current model's context window, not min across all", async () => {
|
||||
const { agent, updates } = createMockAgentWithCapture();
|
||||
injectSession(agent, [
|
||||
@@ -2255,6 +2732,7 @@ describe("emitRawSDKMessages", () => {
|
||||
nextPendingOrder: 0,
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages,
|
||||
contextWindowSize: 200000,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,10 @@ describe("session config options", () => {
|
||||
cancelled: false,
|
||||
permissionMode: "default",
|
||||
settingsManager: {},
|
||||
modes: structuredClone(MOCK_MODES),
|
||||
models: structuredClone(MOCK_MODELS),
|
||||
configOptions: structuredClone(MOCK_CONFIG_OPTIONS),
|
||||
contextWindowSize: 200000,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
148
src/tests/session-load.test.ts
Normal file
148
src/tests/session-load.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { randomUUID } from "crypto";
|
||||
import { AgentSideConnection, RequestError, SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import { query, getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { ClaudeAcpAgent } from "../acp-agent.js";
|
||||
import { Pushable } from "../utils.js";
|
||||
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
function createMockClient(): AgentSideConnection {
|
||||
return {
|
||||
sessionUpdate: async (_notification: SessionNotification) => {},
|
||||
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
|
||||
readTextFile: async () => ({ content: "" }),
|
||||
writeTextFile: async () => ({}),
|
||||
} as unknown as AgentSideConnection;
|
||||
}
|
||||
|
||||
describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("session load/resume lifecycle", () => {
|
||||
it("SDK: session created but never prompted has no messages and is not resumable", async () => {
|
||||
// Create a session via the SDK, initialize it, but never send a prompt
|
||||
const sessionId = randomUUID();
|
||||
const input = new Pushable<SDKUserMessage>();
|
||||
|
||||
const q = query({
|
||||
prompt: input,
|
||||
options: {
|
||||
systemPrompt: { type: "preset", preset: "claude_code" },
|
||||
sessionId,
|
||||
settingSources: ["user", "project", "local"],
|
||||
includePartialMessages: true,
|
||||
},
|
||||
});
|
||||
|
||||
// initializationResult() works without needing a prompt pushed
|
||||
const initResult = await q.initializationResult();
|
||||
expect(initResult).toBeDefined();
|
||||
|
||||
// Close without ever prompting
|
||||
input.end();
|
||||
q.return(undefined);
|
||||
|
||||
// Verify no messages were stored
|
||||
const messages = await getSessionMessages(sessionId);
|
||||
expect(messages).toEqual([]);
|
||||
|
||||
// Verify the session is not resumable
|
||||
const input2 = new Pushable<SDKUserMessage>();
|
||||
const q2 = query({
|
||||
prompt: input2,
|
||||
options: {
|
||||
systemPrompt: { type: "preset", preset: "claude_code" },
|
||||
resume: sessionId,
|
||||
settingSources: ["user", "project", "local"],
|
||||
includePartialMessages: true,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(q2.initializationResult()).rejects.toThrow(
|
||||
/No conversation found with session ID/,
|
||||
);
|
||||
|
||||
input2.end();
|
||||
q2.return(undefined);
|
||||
}, 30000);
|
||||
|
||||
it("ACP: loadSession throws resourceNotFound for a non-existent session", async () => {
|
||||
const agent = new ClaudeAcpAgent(createMockClient());
|
||||
const bogusSessionId = randomUUID();
|
||||
|
||||
try {
|
||||
await expect(
|
||||
agent.loadSession({
|
||||
sessionId: bogusSessionId,
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
}),
|
||||
).rejects.toThrow(RequestError);
|
||||
} finally {
|
||||
await agent.dispose();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
it("ACP: resumeSession throws resourceNotFound for a non-existent session", async () => {
|
||||
const agent = new ClaudeAcpAgent(createMockClient());
|
||||
const bogusSessionId = randomUUID();
|
||||
|
||||
try {
|
||||
await expect(
|
||||
agent.unstable_resumeSession({
|
||||
sessionId: bogusSessionId,
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
}),
|
||||
).rejects.toThrow(RequestError);
|
||||
} finally {
|
||||
await agent.dispose();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
it("ACP: newSession without prompt, then loadSession on fresh agent throws resourceNotFound", async () => {
|
||||
// Step 1: Create a real session via ACP, never prompt, dispose
|
||||
const agentA = new ClaudeAcpAgent(createMockClient());
|
||||
const { sessionId } = await agentA.newSession({
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
});
|
||||
expect(sessionId).toBeDefined();
|
||||
await agentA.dispose();
|
||||
|
||||
// Step 2: Fresh agent tries to load that session
|
||||
const agentB = new ClaudeAcpAgent(createMockClient());
|
||||
|
||||
try {
|
||||
await expect(
|
||||
agentB.loadSession({
|
||||
sessionId,
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
}),
|
||||
).rejects.toThrow(RequestError);
|
||||
} finally {
|
||||
await agentB.dispose();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
it("ACP: newSession without prompt, then resumeSession on fresh agent throws resourceNotFound", async () => {
|
||||
const agentA = new ClaudeAcpAgent(createMockClient());
|
||||
const { sessionId } = await agentA.newSession({
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
});
|
||||
await agentA.dispose();
|
||||
|
||||
const agentB = new ClaudeAcpAgent(createMockClient());
|
||||
|
||||
try {
|
||||
await expect(
|
||||
agentB.unstable_resumeSession({
|
||||
sessionId,
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
}),
|
||||
).rejects.toThrow(RequestError);
|
||||
} finally {
|
||||
await agentB.dispose();
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
Reference in New Issue
Block a user