mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-04 00:12:14 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30b7c06f76 | ||
|
|
c7f5b8fe76 | ||
|
|
7a70f82739 | ||
|
|
e67dacdcac | ||
|
|
61ae8609d4 | ||
|
|
2c19974d5b | ||
|
|
c7dff3cf7e | ||
|
|
3ba2d367a2 | ||
|
|
f4e750decf | ||
|
|
9cd48c597a | ||
|
|
3500ef736f | ||
|
|
8b366d8e9a | ||
|
|
5144aabc52 | ||
|
|
f664ced76d | ||
|
|
e944ceddea | ||
|
|
32b93501d7 | ||
|
|
f08c4521ec | ||
|
|
f3d8ae3eb3 | ||
|
|
b03318f59c | ||
|
|
2bf865eb42 | ||
|
|
1612c07895 | ||
|
|
d74dd1d9d3 | ||
|
|
f1bf4da891 | ||
|
|
837989ef96 | ||
|
|
ff2a8a9635 |
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -50,8 +50,6 @@ jobs:
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm publish
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.55.0"
|
||||
".": "0.59.0"
|
||||
}
|
||||
|
||||
59
CHANGELOG.md
59
CHANGELOG.md
@@ -1,5 +1,64 @@
|
||||
# Changelog
|
||||
|
||||
## [0.59.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.58.1...v0.59.0) (2026-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps-dev:** bump nanoid from 3.3.15 to 3.3.16 ([#875](https://github.com/agentclientprotocol/claude-agent-acp/issues/875)) ([e67dacd](https://github.com/agentclientprotocol/claude-agent-acp/commit/e67dacdcac629b00e5bdcda94b3de57459696b1e))
|
||||
* **deps:** bump @anthropic-ai/claude-agent-sdk to 0.3.207 ([#874](https://github.com/agentclientprotocol/claude-agent-acp/issues/874)) ([c7f5b8f](https://github.com/agentclientprotocol/claude-agent-acp/commit/c7f5b8fe768507afa28cdffe5cc3c054c56306ad))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add subagent parent tool use attribution ([#859](https://github.com/agentclientprotocol/claude-agent-acp/issues/859)) ([9cd48c5](https://github.com/agentclientprotocol/claude-agent-acp/commit/9cd48c597af68f630774ec00bc3adc85f6b0fd4b))
|
||||
* forward result text when the turn emitted no assistant message ([#858](https://github.com/agentclientprotocol/claude-agent-acp/issues/858)) ([61ae860](https://github.com/agentclientprotocol/claude-agent-acp/commit/61ae8609d4343da6758c2376c6cf684c7fb0956a))
|
||||
* hold a turn open while its background subagents are still live ([#870](https://github.com/agentclientprotocol/claude-agent-acp/issues/870)) ([7a70f82](https://github.com/agentclientprotocol/claude-agent-acp/commit/7a70f82739e085014cad878f08513cdef7b7fe16))
|
||||
* Refine tool calls from streamed input ([#867](https://github.com/agentclientprotocol/claude-agent-acp/issues/867)) ([2c19974](https://github.com/agentclientprotocol/claude-agent-acp/commit/2c19974d5b80d3178b35e80a4bb0a1050588aab9))
|
||||
* Seed context window from SDK usage report ([#868](https://github.com/agentclientprotocol/claude-agent-acp/issues/868)) ([3ba2d36](https://github.com/agentclientprotocol/claude-agent-acp/commit/3ba2d367a282fe3053a73804a416a435cb01ee08)), closes [#596](https://github.com/agentclientprotocol/claude-agent-acp/issues/596)
|
||||
* Skip synthetic login messages on replay ([#869](https://github.com/agentclientprotocol/claude-agent-acp/issues/869)) ([c7dff3c](https://github.com/agentclientprotocol/claude-agent-acp/commit/c7dff3cf7eeb03cb993493efafa96344f18de81b)), closes [#863](https://github.com/agentclientprotocol/claude-agent-acp/issues/863)
|
||||
|
||||
## [0.58.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.58.0...v0.58.1) (2026-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use valid npm version for publish ([#855](https://github.com/agentclientprotocol/claude-agent-acp/issues/855)) ([8b366d8](https://github.com/agentclientprotocol/claude-agent-acp/commit/8b366d8e9ad55e9237bf2d618cfc99b57fac570d))
|
||||
|
||||
## [0.58.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.57.0...v0.58.0) (2026-07-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** update to @anthropic-ai/claude-agent-sdk 0.3.205 ([#854](https://github.com/agentclientprotocol/claude-agent-acp/issues/854)) ([f664ced](https://github.com/agentclientprotocol/claude-agent-acp/commit/f664ced76df00674d88aedd018a4261da3bc3f3a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Preserve live model on resumed sessions ([#848](https://github.com/agentclientprotocol/claude-agent-acp/issues/848)) ([f3d8ae3](https://github.com/agentclientprotocol/claude-agent-acp/commit/f3d8ae3eb389ee9367f48c8762562a55280ade0a)), closes [#845](https://github.com/agentclientprotocol/claude-agent-acp/issues/845)
|
||||
* Report usage for cancelled active turns ([#846](https://github.com/agentclientprotocol/claude-agent-acp/issues/846)) ([b03318f](https://github.com/agentclientprotocol/claude-agent-acp/commit/b03318f59c6165146c8d8e036227f35c3abd0ff4)), closes [#844](https://github.com/agentclientprotocol/claude-agent-acp/issues/844)
|
||||
* tolerate missing text in streamed thinking chunks ([#852](https://github.com/agentclientprotocol/claude-agent-acp/issues/852)) ([e944ced](https://github.com/agentclientprotocol/claude-agent-acp/commit/e944ceddea79950d6bfb6339266862507c4b964f))
|
||||
* Use SDK guards for elicitation validation ([#850](https://github.com/agentclientprotocol/claude-agent-acp/issues/850)) ([32b9350](https://github.com/agentclientprotocol/claude-agent-acp/commit/32b93501d7a35a3b405b5022b4caaad492eed11c))
|
||||
|
||||
## [0.57.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.56.0...v0.57.0) (2026-07-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update @anthropic-ai/claude-agent-sdk to 0.3.202 ([#843](https://github.com/agentclientprotocol/claude-agent-acp/issues/843)) ([1612c07](https://github.com/agentclientprotocol/claude-agent-acp/commit/1612c078953c134e12368a2d968a9bcf14d31b55))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't emit empty agent_message_chunk ([#841](https://github.com/agentclientprotocol/claude-agent-acp/issues/841)) ([d74dd1d](https://github.com/agentclientprotocol/claude-agent-acp/commit/d74dd1d9d356050fc87f9fdab8599f1546b12a2c))
|
||||
|
||||
## [0.56.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.55.0...v0.56.0) (2026-07-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** bump @anthropic-ai/claude-agent-sdk to 0.3.201 ([#837](https://github.com/agentclientprotocol/claude-agent-acp/issues/837)) ([ff2a8a9](https://github.com/agentclientprotocol/claude-agent-acp/commit/ff2a8a963536a4265700e4f2003a0ffff93c9087))
|
||||
|
||||
## [0.55.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.54.1...v0.55.0) (2026-07-02)
|
||||
|
||||
|
||||
|
||||
504
package-lock.json
generated
504
package-lock.json
generated
@@ -1,66 +1,66 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.55.0",
|
||||
"version": "0.59.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.55.0",
|
||||
"version": "0.59.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.1.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.198",
|
||||
"@agentclientprotocol/sdk": "1.2.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.207",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.109.1",
|
||||
"@anthropic-ai/sdk": "0.111.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.5",
|
||||
"@types/node": "26.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.62.1",
|
||||
"@typescript-eslint/parser": "8.62.1",
|
||||
"eslint": "10.6.0",
|
||||
"@types/node": "26.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"eslint": "10.7.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.7.0",
|
||||
"prettier": "3.9.4",
|
||||
"prettier": "3.9.5",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.9"
|
||||
"vitest": "4.1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.1.0.tgz",
|
||||
"integrity": "sha512-NT2KqphUJ3w6EksUL51ZhJgIYgq/ZLGcBPkyMKgRSO5PMVwe9DnKKX+Htnvk6KHh6dUuh34UHK4gKp+4te1Mdg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.2.1.tgz",
|
||||
"integrity": "sha512-jwYUdOQR7tc+Zfch53VL4JJyUNK/46q03uUTYb+PjECsmnNl94XFXOfYLJ8RBpMNidXd1rpOAVgb0vqD98xImA==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.198.tgz",
|
||||
"integrity": "sha512-xt469sSCyclTPtzpLAg0Aschy665GiRMgZKabSmESbGUA5/H56HcILVOiFxclXswkeMUk2fQxfHJUY9UZfiTnA==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.207.tgz",
|
||||
"integrity": "sha512-y0PkQRmQBi96MHiN5Xzfq+GaddxCZCqI/cXEQBLYBLXGa4i1nDSlulQqkMBj2RorrrSGQJ6Wdw+uhu6OfHNPzA==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.198",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.198",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.198",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.198",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.198",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.198",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.198",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.198"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.207"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -69,9 +69,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.198.tgz",
|
||||
"integrity": "sha512-ZmiAybQKIKcP1qEAE/vfXvfxtKxG9CnJn98QTXC5Zxiwuy7Mllx2ALXh9dfmsf0V87CGEodlZQmMgUJotNIsUw==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.207.tgz",
|
||||
"integrity": "sha512-08xSo1FDx8h0aLhL5tvcRxa2SMmcUV3aDWeZiEJVTclyiDAs61BgTjAxCg+SZcu1CndjJO8cfO0yM5dhamxz3g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -82,9 +82,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.198.tgz",
|
||||
"integrity": "sha512-XwH5vgN46WSwg8aC1OagNofnJpV/G1ciEu118GEKer8ZhVkq/dvK/DqShxMkb6r1jV7u5IJ7zPXu9uKliyNJAw==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.207.tgz",
|
||||
"integrity": "sha512-1o7K4EYqyCixZ/oeOZSh7AzSy6TM86xoOuf4VuORjPSS31hBnoqY0NGZd27+2VDs9LGtsdksmsTqcNGx9xd1hA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -95,9 +95,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.198.tgz",
|
||||
"integrity": "sha512-qmz8dxEtDIlKntU5qYe0R4aWTxTue5S7zIQknatLX7aJ6HN/nq1aCNXWn5smTH2FViBkUPPR+sCIsNwSk6AT6Q==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.207.tgz",
|
||||
"integrity": "sha512-X4uezYOifDiNTTmmugfRCdg3nNamrr1LFRY9hg30vWYTShL+bbN+nfC3KaFfSYCl4GTtsEEUbYdOTC2F3bBpcA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -111,9 +111,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.198.tgz",
|
||||
"integrity": "sha512-Q7lKVNjIrUQ2B/AR77OvRf0zeOdEjonFVaR9FYrrwtzGeEqum69WSht5nM7Y7el3wjbNi0/eV0QTUM0DlsTEfw==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.207.tgz",
|
||||
"integrity": "sha512-oPj+g2DslhH4Y9nCTs7al4t9wZv78FZwLFQwOCg99BXuz1o0ZOpKmxyvR7J9eBR+GPszeMMS8gYplQTiZC9o2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -127,9 +127,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.198.tgz",
|
||||
"integrity": "sha512-Zqxyz2AT1UM5WlOOoLJhLssZDgZo8rBK5ku6daveK12zp+UTJGZhGsjFghz1/ASxH08KqOTbUePNTORnPhHAEQ==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.207.tgz",
|
||||
"integrity": "sha512-Kg6BPH8Ee0ny/oEUWJmvT1jCRBne4jVpRSOMsJcYp1Fav1rMEgpU219oJJs+LWwx4ifuuLtNWedqJNnVw7mnKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -143,9 +143,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.198.tgz",
|
||||
"integrity": "sha512-h1SrWVIMjLInYNPlf+TxXuKTOdoiOfJLBSoQG97315Z2Nh0IpBfqWExlqYTtPCgKE7q2iga31U283QfHpIDlSQ==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.207.tgz",
|
||||
"integrity": "sha512-uRv+D5oG/7EYr41FAJ9IPo2pZYBe2ZMaA6nSHCeizsgPxCSMtl5bNppmU21+jZJvo4hivObEgkGFERAhdGqygg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -159,9 +159,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.198.tgz",
|
||||
"integrity": "sha512-mjIHf1HFiRuXefewWTaNZFlTZlCaEt/xsRjc1nSTCEEpFolZayVhrDKz+O2QFVcDtPl8x8GeYSL0kiikg1DZjQ==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.207.tgz",
|
||||
"integrity": "sha512-9fWpUzfkXlPAg2tf8JpQe7w9avFaomAUbfAwyAmykQgSIf66LwaJjvI5hNqhNqczRKyfsXPn3ei2S5HKlmFP+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -172,9 +172,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.198",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.198.tgz",
|
||||
"integrity": "sha512-y3HLuCCz1kDwUrhd6OnqO+d5BUpTFSzNUsPT9kf3r1vk9HYKF+eMC9eIlcOhiW2kX491kxEvuEOfqgIkGx15cg==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.207.tgz",
|
||||
"integrity": "sha512-YPjVT0q6aXEM2MgN4CI6/9fqiTXwETji+4NoPOzCYuqAkhXZqp30Jsk7/NHqYGNNSfURKrsuAoliKB0rsbpbjg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -185,9 +185,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.109.1",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.109.1.tgz",
|
||||
"integrity": "sha512-q9OnEKLr5H9nxSuXdgDgJhxfYMiE+AaUEBze2Gk91UcaaLnsN+Lx5fbCYywiqurU/APLdwv23x03Wm6WN3EBsg==",
|
||||
"version": "0.111.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.111.0.tgz",
|
||||
"integrity": "sha512-1hUqKi+uJQoS5X90+InwHbFAXMvgq0DnsC5hVLEeSRaODiU5WvmqDAcVCmGS2wC0pN9Z8jtWCbWw7JLzeDdm/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
@@ -544,9 +544,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.138.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz",
|
||||
"integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
|
||||
"version": "0.139.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
|
||||
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -554,9 +554,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -571,9 +571,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -588,9 +588,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
|
||||
"integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -605,9 +605,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
|
||||
"integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -622,9 +622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
|
||||
"integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
|
||||
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -639,9 +639,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -659,9 +659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
|
||||
"integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -679,9 +679,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -699,9 +699,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -719,9 +719,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -739,9 +739,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
|
||||
"integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -759,9 +759,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -776,9 +776,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
|
||||
"integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
|
||||
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -795,9 +795,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
|
||||
"integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -812,9 +812,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
|
||||
"integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -934,9 +934,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz",
|
||||
"integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==",
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -944,17 +944,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz",
|
||||
"integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz",
|
||||
"integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.62.1",
|
||||
"@typescript-eslint/type-utils": "8.62.1",
|
||||
"@typescript-eslint/utils": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/type-utils": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -967,22 +967,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.62.1",
|
||||
"@typescript-eslint/parser": "^8.63.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.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz",
|
||||
"integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz",
|
||||
"integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.62.1",
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -998,14 +998,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz",
|
||||
"integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
|
||||
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.62.1",
|
||||
"@typescript-eslint/types": "^8.62.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.63.0",
|
||||
"@typescript-eslint/types": "^8.63.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1020,14 +1020,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz",
|
||||
"integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
|
||||
"integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1"
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1038,9 +1038,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz",
|
||||
"integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1055,15 +1055,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz",
|
||||
"integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1",
|
||||
"@typescript-eslint/utils": "8.62.1",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -1080,9 +1080,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz",
|
||||
"integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
|
||||
"integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1094,16 +1094,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz",
|
||||
"integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
|
||||
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.62.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.62.1",
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1",
|
||||
"@typescript-eslint/project-service": "8.63.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1122,16 +1122,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz",
|
||||
"integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
|
||||
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.62.1",
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1"
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1146,13 +1146,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz",
|
||||
"integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz",
|
||||
"integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1177,16 +1177,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
|
||||
"integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
|
||||
"integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.9",
|
||||
"@vitest/utils": "4.1.9",
|
||||
"@vitest/spy": "4.1.10",
|
||||
"@vitest/utils": "4.1.10",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -1195,13 +1195,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
|
||||
"integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
|
||||
"integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.9",
|
||||
"@vitest/spy": "4.1.10",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -1222,9 +1222,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
|
||||
"integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
|
||||
"integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1235,13 +1235,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
|
||||
"integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
|
||||
"integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.9",
|
||||
"@vitest/utils": "4.1.10",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -1249,14 +1249,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
|
||||
"integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
|
||||
"integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.9",
|
||||
"@vitest/utils": "4.1.9",
|
||||
"@vitest/pretty-format": "4.1.10",
|
||||
"@vitest/utils": "4.1.10",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -1265,9 +1265,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
|
||||
"integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
|
||||
"integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -1275,13 +1275,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
|
||||
"integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
|
||||
"integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.9",
|
||||
"@vitest/pretty-format": "4.1.10",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -1701,9 +1701,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz",
|
||||
"integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
|
||||
"integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1741,9 +1741,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz",
|
||||
"integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==",
|
||||
"version": "10.7.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz",
|
||||
"integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -2371,9 +2371,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.27",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz",
|
||||
"integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==",
|
||||
"version": "4.12.30",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.30.tgz",
|
||||
"integrity": "sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -2402,9 +2402,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
|
||||
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -2965,9 +2965,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.15",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
|
||||
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3165,9 +3165,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3188,9 +3188,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
|
||||
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
|
||||
"version": "8.5.19",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
|
||||
"integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3227,9 +3227,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.9.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz",
|
||||
"integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==",
|
||||
"version": "3.9.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz",
|
||||
"integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -3324,13 +3324,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz",
|
||||
"integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.138.0",
|
||||
"@oxc-project/types": "=0.139.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3340,21 +3340,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.1.4",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.4",
|
||||
"@rolldown/binding-darwin-x64": "1.1.4",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.4",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.4",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.4",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.4",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.4",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.4",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.4"
|
||||
"@rolldown/binding-android-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-x64": "1.1.5",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.5",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.5",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.5",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.5",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.5",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.5",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
@@ -3590,9 +3590,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
|
||||
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
|
||||
"integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -3826,16 +3826,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
|
||||
"integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz",
|
||||
"integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"picomatch": "^4.0.5",
|
||||
"postcss": "^8.5.16",
|
||||
"rolldown": "~1.1.3",
|
||||
"rolldown": "~1.1.4",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3904,19 +3904,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
|
||||
"integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
|
||||
"integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.9",
|
||||
"@vitest/mocker": "4.1.9",
|
||||
"@vitest/pretty-format": "4.1.9",
|
||||
"@vitest/runner": "4.1.9",
|
||||
"@vitest/snapshot": "4.1.9",
|
||||
"@vitest/spy": "4.1.9",
|
||||
"@vitest/utils": "4.1.9",
|
||||
"@vitest/expect": "4.1.10",
|
||||
"@vitest/mocker": "4.1.10",
|
||||
"@vitest/pretty-format": "4.1.10",
|
||||
"@vitest/runner": "4.1.10",
|
||||
"@vitest/snapshot": "4.1.10",
|
||||
"@vitest/spy": "4.1.10",
|
||||
"@vitest/utils": "4.1.10",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
@@ -3944,12 +3944,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.9",
|
||||
"@vitest/browser-preview": "4.1.9",
|
||||
"@vitest/browser-webdriverio": "4.1.9",
|
||||
"@vitest/coverage-istanbul": "4.1.9",
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
"@vitest/ui": "4.1.9",
|
||||
"@vitest/browser-playwright": "4.1.10",
|
||||
"@vitest/browser-preview": "4.1.10",
|
||||
"@vitest/browser-webdriverio": "4.1.10",
|
||||
"@vitest/coverage-istanbul": "4.1.10",
|
||||
"@vitest/coverage-v8": "4.1.10",
|
||||
"@vitest/ui": "4.1.10",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
|
||||
20
package.json
20
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.55.0",
|
||||
"version": "0.59.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -63,23 +63,23 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.1.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.198",
|
||||
"@agentclientprotocol/sdk": "1.2.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.207",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.109.1",
|
||||
"@anthropic-ai/sdk": "0.111.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.5",
|
||||
"@types/node": "26.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.62.1",
|
||||
"@typescript-eslint/parser": "8.62.1",
|
||||
"eslint": "10.6.0",
|
||||
"@types/node": "26.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"eslint": "10.7.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.7.0",
|
||||
"prettier": "3.9.4",
|
||||
"prettier": "3.9.5",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.9"
|
||||
"vitest": "4.1.10"
|
||||
}
|
||||
}
|
||||
|
||||
2381
src/acp-agent.ts
2381
src/acp-agent.ts
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,8 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { CreateElicitationResponse } from "@agentclientprotocol/sdk";
|
||||
import type {
|
||||
CreateElicitationRequest,
|
||||
CreateElicitationResponse,
|
||||
ElicitationAcceptAction,
|
||||
ElicitationPropertySchema,
|
||||
ElicitationSchema,
|
||||
EnumOption,
|
||||
@@ -68,6 +69,20 @@ export function mcpElicitationToCreateRequest(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Content of an accepted elicitation response.
|
||||
*
|
||||
* Uses the SDK's validating guard rather than an `action === "accept"` check:
|
||||
* the guard both narrows past the union's custom/future variant and validates
|
||||
* the payload, so a malformed accept (right tag, ill-typed content) yields
|
||||
* empty content — the same classification the SDK's wire validators apply.
|
||||
*/
|
||||
function acceptedElicitationContent(
|
||||
response: CreateElicitationResponse,
|
||||
): NonNullable<ElicitationAcceptAction["content"]> {
|
||||
return CreateElicitationResponse.isAccept(response) ? (response.content ?? {}) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an ACP elicitation response back to the MCP `ElicitResult` the SDK expects
|
||||
* to hand back to the requesting server.
|
||||
@@ -77,7 +92,7 @@ export function createElicitationResponseToElicitResult(
|
||||
): ElicitationResult {
|
||||
switch (response.action) {
|
||||
case "accept":
|
||||
return { action: "accept", content: response.content ?? {} };
|
||||
return { action: "accept", content: acceptedElicitationContent(response) };
|
||||
case "decline":
|
||||
return { action: "decline" };
|
||||
case "cancel":
|
||||
@@ -126,33 +141,13 @@ function questionCustomFieldKey(index: number): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* `_meta` key under which a bridged enum option carries its structured
|
||||
* `description`/`preview`, since ACP's `EnumOption` has no field for either.
|
||||
* Namespaced like the agent's other `_meta` extensions (`_claude/...`).
|
||||
* `_meta` key under which a bridged enum option carries its `preview`, the one
|
||||
* option field ACP's `EnumOption` still has no slot for (descriptions are
|
||||
* first-class as of schema 1.19). Namespaced like the agent's other `_meta`
|
||||
* extensions (`_claude/...`).
|
||||
*/
|
||||
const OPTION_META_KEY = "_claude/askUserQuestionOption";
|
||||
|
||||
/** A single option as supplied by the AskUserQuestion tool. */
|
||||
type AskUserQuestionOption = AskUserQuestion["options"][number];
|
||||
|
||||
/**
|
||||
* Build the `_meta` payload that carries an option's structured `description`
|
||||
* and optional `preview` for clients that can render them as distinct fields.
|
||||
* Returns `undefined` when neither is present, so we don't emit an empty `_meta`.
|
||||
*/
|
||||
function askUserQuestionOptionMeta(
|
||||
option: AskUserQuestionOption,
|
||||
): NonNullable<EnumOption["_meta"]> | undefined {
|
||||
const detail: { description?: string; preview?: string } = {};
|
||||
if (option.description) {
|
||||
detail.description = option.description;
|
||||
}
|
||||
if (option.preview) {
|
||||
detail.preview = option.preview;
|
||||
}
|
||||
return Object.keys(detail).length > 0 ? { [OPTION_META_KEY]: detail } : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the AskUserQuestion tool's questions as an ACP form elicitation.
|
||||
*
|
||||
@@ -160,7 +155,8 @@ function askUserQuestionOptionMeta(
|
||||
* question text, so the question text appears in exactly one place per field.
|
||||
* Single-select questions use a titled `oneOf` enum; multi-select questions use
|
||||
* an array with a titled `anyOf` item enum. The enum `const` is always the
|
||||
* option label, since that is what the tool records as the answer.
|
||||
* option label, since that is what the tool records as the answer; an option's
|
||||
* secondary text travels in the enum option's own `description` field.
|
||||
*
|
||||
* Each question is followed by its own optional free-text "custom answer" field
|
||||
* (`question_<n>_custom`), mirroring the CLI's per-question "Other" box: the
|
||||
@@ -180,19 +176,16 @@ export function askUserQuestionsToCreateRequest(
|
||||
const options: EnumOption[] = question.options.map((option) => {
|
||||
const enumOption: EnumOption = {
|
||||
const: option.label,
|
||||
// `title` stays a flattened "label — description" string so clients that
|
||||
// only read `const`/`title` still surface the description. Clients that
|
||||
// understand the `_meta` below should prefer its structured fields (and
|
||||
// treat `const` as the clean label) rather than parsing this title.
|
||||
title: option.description ? `${option.label} — ${option.description}` : option.label,
|
||||
title: option.label,
|
||||
};
|
||||
// The SDK option carries a `description` (secondary text) and an optional
|
||||
// `preview` (mockups, code snippets, comparisons shown on focus). Neither
|
||||
// has a structural slot in `EnumOption`, so forward them under ACP's
|
||||
// reserved `_meta` extension point for clients that can render them.
|
||||
const meta = askUserQuestionOptionMeta(option);
|
||||
if (meta) {
|
||||
enumOption._meta = meta;
|
||||
if (option.description) {
|
||||
enumOption.description = option.description;
|
||||
}
|
||||
// The SDK option's `preview` (mockups, code snippets, comparisons shown
|
||||
// on focus) still has no structural slot in `EnumOption`, so forward it
|
||||
// under ACP's reserved `_meta` extension point for clients that render it.
|
||||
if (option.preview) {
|
||||
enumOption._meta = { [OPTION_META_KEY]: { preview: option.preview } };
|
||||
}
|
||||
return enumOption;
|
||||
});
|
||||
@@ -243,22 +236,23 @@ export type AskUserQuestionOutcome =
|
||||
* custom-answer field (`question_<n>_custom`) takes precedence over that
|
||||
* question's selection, since the user typed their own answer instead of
|
||||
* picking one. Decline yields empty answers (the model is told the user skipped
|
||||
* rather than the turn aborting); cancel aborts the tool call.
|
||||
* rather than the turn aborting); cancel — and any custom/future action we
|
||||
* don't understand — aborts the tool call.
|
||||
*/
|
||||
export function applyAskElicitationResponse(
|
||||
response: CreateElicitationResponse,
|
||||
toolInput: Record<string, unknown>,
|
||||
questions: AskUserQuestion[],
|
||||
): AskUserQuestionOutcome {
|
||||
if (response.action === "cancel") {
|
||||
return { action: "cancel" };
|
||||
}
|
||||
|
||||
if (response.action === "decline") {
|
||||
return { action: "answered", updatedInput: { ...toolInput, answers: {} } };
|
||||
}
|
||||
|
||||
const content = response.content ?? {};
|
||||
if (response.action !== "accept") {
|
||||
return { action: "cancel" };
|
||||
}
|
||||
|
||||
const content = acceptedElicitationContent(response);
|
||||
// Typed against the tool's own output schema so the answer/response shapes
|
||||
// stay in sync with what the built-in tool's call() expects to read back.
|
||||
const answers: AskUserQuestionOutput["answers"] = {};
|
||||
@@ -372,7 +366,7 @@ export function refusalFallbackToCreateRequest(
|
||||
sessionId,
|
||||
message:
|
||||
`${prompt.originalModel} declined this request${category}. ` +
|
||||
`Retry with ${prompt.fallbackModel}? The session would continue on ${prompt.fallbackModel}.` +
|
||||
`Retry with ${prompt.fallbackModel}?` +
|
||||
guidance,
|
||||
requestedSchema: {
|
||||
type: "object",
|
||||
@@ -380,8 +374,16 @@ export function refusalFallbackToCreateRequest(
|
||||
[REFUSAL_FALLBACK_CHOICE_KEY]: {
|
||||
type: "string",
|
||||
oneOf: [
|
||||
{ const: RETRY_FALLBACK_RESULT, title: `Retry with ${prompt.fallbackModel}` },
|
||||
{ const: KEEP_REFUSAL_RESULT, title: "Keep the refusal" },
|
||||
{
|
||||
const: RETRY_FALLBACK_RESULT,
|
||||
title: `Retry with ${prompt.fallbackModel}`,
|
||||
description: `The session continues on ${prompt.fallbackModel}.`,
|
||||
},
|
||||
{
|
||||
const: KEEP_REFUSAL_RESULT,
|
||||
title: "Keep the refusal",
|
||||
description: "You can send a new message.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -397,9 +399,6 @@ export function refusalFallbackToCreateRequest(
|
||||
* switch the user didn't ask for.
|
||||
*/
|
||||
export function refusalFallbackResultFromResponse(response: CreateElicitationResponse): string {
|
||||
if (response.action !== "accept") {
|
||||
return KEEP_REFUSAL_RESULT;
|
||||
}
|
||||
const choice = response.content?.[REFUSAL_FALLBACK_CHOICE_KEY];
|
||||
const choice = acceptedElicitationContent(response)[REFUSAL_FALLBACK_CHOICE_KEY];
|
||||
return choice === RETRY_FALLBACK_RESULT ? RETRY_FALLBACK_RESULT : KEEP_REFUSAL_RESULT;
|
||||
}
|
||||
|
||||
38
src/sdk-shims.d.ts
vendored
38
src/sdk-shims.d.ts
vendored
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Shims for types the `@anthropic-ai/claude-agent-sdk` 0.3.198 `sdk.d.ts`
|
||||
* references in its `SDKMessage` union but never declares
|
||||
* (`SDKControlRequestProgressMessage`, `SDKConversationResetMessage`). With
|
||||
* `skipLibCheck` the dangling references silently become error-`any`, which
|
||||
* poisons narrowing on the whole union — every exhaustive switch over
|
||||
* `SDKMessage` stops compiling.
|
||||
*
|
||||
* These must stay GLOBAL declarations (no top-level import/export in this
|
||||
* file): unqualified names inside the SDK's module declaration fall back to
|
||||
* the global scope, which is the only way a consumer can supply them. Shapes
|
||||
* are transcribed from the zod schemas embedded in the bundled CLI binary.
|
||||
* Delete this file once the SDK ships the declarations itself — a proper
|
||||
* module-scoped declaration will shadow these.
|
||||
*/
|
||||
|
||||
/** Progress for a long-running client-originated control request (currently
|
||||
* only `side_question`), correlated by `request_id`. */
|
||||
declare type SDKControlRequestProgressMessage = {
|
||||
type: "control_request_progress";
|
||||
request_id: string;
|
||||
status: "started" | "api_retry";
|
||||
attempt?: number;
|
||||
max_retries?: number;
|
||||
retry_delay_ms?: number;
|
||||
error_status?: number | null;
|
||||
uuid: import("crypto").UUID;
|
||||
session_id: string;
|
||||
};
|
||||
|
||||
/** Emitted by `/clear`, plan-mode exit, and fresh-session flows; the surface
|
||||
* should mount a fresh transcript under `new_conversation_id`. */
|
||||
declare type SDKConversationResetMessage = {
|
||||
type: "conversation_reset";
|
||||
new_conversation_id: import("crypto").UUID;
|
||||
uuid: import("crypto").UUID;
|
||||
session_id: string;
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
|
||||
import { makeMockQuery } from "./helpers.js";
|
||||
|
||||
const { querySpy } = vi.hoisted(() => ({
|
||||
querySpy: vi.fn(),
|
||||
@@ -34,7 +35,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(({ options }: any) => {
|
||||
capturedOptions = options;
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
@@ -45,8 +46,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
return { getCapturedOptions: () => capturedOptions, setModelSpy };
|
||||
}
|
||||
@@ -240,12 +240,11 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
const setPermissionModeSpy = vi.fn();
|
||||
querySpy.mockImplementation(({ options }: any) => {
|
||||
capturedOptions = options;
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({ models }),
|
||||
setModel: setModelSpy,
|
||||
setPermissionMode: setPermissionModeSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
return {
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
@@ -396,11 +395,10 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
} {
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(() => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({ models }),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
return { setModelSpy };
|
||||
}
|
||||
@@ -875,7 +873,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(({ options: _options }: any) => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
@@ -891,8 +889,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const { ClaudeAcpAgent } = await import("../acp-agent.js");
|
||||
@@ -923,7 +920,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(() => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{ value: "default", displayName: "Default", description: "" },
|
||||
@@ -932,8 +929,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -977,7 +973,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(() => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{ value: "default", displayName: "Default", description: "" },
|
||||
@@ -985,8 +981,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const { ClaudeAcpAgent } = await import("../acp-agent.js");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,30 +7,30 @@ import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-age
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
let capturedOptions: Options | undefined;
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({
|
||||
...(await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
const actual = await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
"@anthropic-ai/claude-agent-sdk",
|
||||
)),
|
||||
query: ({ options }: { options: Options }) => {
|
||||
capturedOptions = options;
|
||||
return {
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
value: "claude-sonnet-4-6",
|
||||
displayName: "Claude Sonnet",
|
||||
description: "Fast",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
setModel: async () => {},
|
||||
setPermissionMode: async () => {},
|
||||
supportedCommands: async () => [],
|
||||
[Symbol.asyncIterator]: async function* () {},
|
||||
};
|
||||
},
|
||||
}));
|
||||
);
|
||||
const { makeMockQuery } = await import("./helpers.js");
|
||||
return {
|
||||
...actual,
|
||||
query: ({ options }: { options: Options }) => {
|
||||
capturedOptions = options;
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
value: "claude-sonnet-4-6",
|
||||
displayName: "Claude Sonnet",
|
||||
description: "Fast",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock("../tools.js", async () => ({
|
||||
...(await vi.importActual<typeof import("../tools.js")>("../tools.js")),
|
||||
registerHookCallback: vi.fn(),
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { describe, expect, it, Mock, vi, afterEach, beforeEach } from "vitest";
|
||||
import { AcpClient, ClaudeAcpAgent } from "../acp-agent.js";
|
||||
import { makeMockQuery } from "./helpers.js";
|
||||
|
||||
const mockQuery = vi.hoisted(() =>
|
||||
vi.fn(() => ({
|
||||
initializationResult: vi.fn().mockResolvedValue({
|
||||
models: [
|
||||
{ value: "id", displayName: "name", description: "description", supportsAutoMode: true },
|
||||
],
|
||||
}),
|
||||
setModel: vi.fn(),
|
||||
setPermissionMode: vi.fn(),
|
||||
supportedCommands: vi.fn().mockResolvedValue([]),
|
||||
})),
|
||||
);
|
||||
const mockQuery = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({
|
||||
...(await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
@@ -24,6 +14,22 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({
|
||||
describe("authorization", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
// Set here (not in vi.fn(impl) at hoist time) so the helper import is
|
||||
// available; afterEach's resetAllMocks clears it, beforeEach re-sets it.
|
||||
mockQuery.mockImplementation(() =>
|
||||
makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
value: "id",
|
||||
displayName: "name",
|
||||
description: "description",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -7,15 +7,17 @@ import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
let capturedOptions: Options | undefined;
|
||||
let contextUsageResult: (() => Promise<{ rawMaxTokens: number }>) | undefined;
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
const actual = await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
"@anthropic-ai/claude-agent-sdk",
|
||||
);
|
||||
const { makeMockQuery, DEFAULT_CONTEXT_USAGE } = await import("./helpers.js");
|
||||
return {
|
||||
...actual,
|
||||
query: (args: { prompt: unknown; options: Options }) => {
|
||||
capturedOptions = args.options;
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
@@ -26,11 +28,9 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
},
|
||||
],
|
||||
}),
|
||||
setModel: async () => {},
|
||||
setPermissionMode: async () => {},
|
||||
supportedCommands: async () => [],
|
||||
[Symbol.asyncIterator]: async function* () {},
|
||||
};
|
||||
getContextUsage: () =>
|
||||
contextUsageResult ? contextUsageResult() : Promise.resolve(DEFAULT_CONTEXT_USAGE),
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -58,6 +58,7 @@ describe("createSession options merging", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
capturedOptions = undefined;
|
||||
contextUsageResult = undefined;
|
||||
|
||||
vi.resetModules();
|
||||
const acpAgent = await import("../acp-agent.js");
|
||||
@@ -514,8 +515,13 @@ describe("createSession options merging", () => {
|
||||
|
||||
it("ignores a non-numeric value", async () => {
|
||||
process.env.MAX_THINKING_TOKENS = "lots";
|
||||
// The bad value is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
expect(capturedOptions!.thinking).toBeUndefined();
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("MAX_THINKING_TOKENS"));
|
||||
});
|
||||
|
||||
it("lets a user-provided thinking option override the env default", async () => {
|
||||
@@ -614,6 +620,46 @@ describe("createSession options merging", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("context window seeding", () => {
|
||||
function sessionFor(sessionId: string) {
|
||||
return (agent as unknown as { sessions: Record<string, any> }).sessions[sessionId];
|
||||
}
|
||||
|
||||
it("seeds contextWindowSize from the SDK's getContextUsage report", async () => {
|
||||
// Aliases like `sonnet` can resolve to an extended-context model with no
|
||||
// "1m" token anywhere in id/displayName/description, so the authoritative
|
||||
// report must win over text inference (issue #596).
|
||||
contextUsageResult = async () => ({ rawMaxTokens: 967000 });
|
||||
|
||||
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
|
||||
expect(sessionFor(response.sessionId).contextWindowSize).toBe(967000);
|
||||
});
|
||||
|
||||
it("falls back to the default window when getContextUsage fails and inference misses", async () => {
|
||||
// getContextUsage rejects, and the mock model ("claude-sonnet-4-6" /
|
||||
// "Claude Sonnet" / "Fast") has no "1m" hint.
|
||||
contextUsageResult = () => Promise.reject(new Error("no context usage mocked"));
|
||||
// The rejection is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
|
||||
expect(sessionFor(response.sessionId).contextWindowSize).toBe(200000);
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores a nonsensical (non-positive) reported window", async () => {
|
||||
contextUsageResult = async () => ({ rawMaxTokens: 0 });
|
||||
|
||||
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
|
||||
expect(sessionFor(response.sessionId).contextWindowSize).toBe(200000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("refusal fallback dialog", () => {
|
||||
it("declares the dialog kind and callback when the client supports form elicitation", async () => {
|
||||
await agent.initialize({
|
||||
@@ -711,6 +757,10 @@ describe("createSession options merging", () => {
|
||||
|
||||
it("cancels on a malformed payload without presenting anything", async () => {
|
||||
const { onUserDialog, createElicitation } = await setupDialog();
|
||||
// The malformed payload is deliberate — capture the agent's warning
|
||||
// instead of letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
const result = await onUserDialog(
|
||||
{ dialogKind: "refusal_fallback_prompt", payload: { fallbackModel: 42 } },
|
||||
@@ -719,11 +769,16 @@ describe("createSession options merging", () => {
|
||||
|
||||
expect(result).toEqual({ behavior: "cancelled" });
|
||||
expect(createElicitation).not.toHaveBeenCalled();
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("unexpected shape"));
|
||||
});
|
||||
|
||||
it("cancels when the elicitation request fails", async () => {
|
||||
const { onUserDialog, createElicitation } = await setupDialog();
|
||||
createElicitation.mockRejectedValue(new Error("client exploded"));
|
||||
// The client failure is deliberate — capture the agent's warning
|
||||
// instead of letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
const result = await onUserDialog(
|
||||
{
|
||||
@@ -734,6 +789,7 @@ describe("createSession options merging", () => {
|
||||
);
|
||||
|
||||
expect(result).toEqual({ behavior: "cancelled" });
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("client exploded"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ const SESSION_ID = "session-1";
|
||||
/**
|
||||
* Build a question matching the SDK's (strict) AskUserQuestion schema while
|
||||
* keeping test sites terse. Option descriptions default to "" (the tool always
|
||||
* provides one); pass a description to exercise the "label — description" title.
|
||||
* provides one); pass a description to exercise the enum option's description.
|
||||
*/
|
||||
function mkQuestion(
|
||||
question: string,
|
||||
@@ -127,6 +127,14 @@ describe("createElicitationResponseToElicitResult", () => {
|
||||
createElicitationResponseToElicitResult({ action: "cancel" } as CreateElicitationResponse),
|
||||
).toEqual({ action: "cancel" });
|
||||
});
|
||||
|
||||
it("maps a custom/future action to cancel", () => {
|
||||
expect(
|
||||
createElicitationResponseToElicitResult({
|
||||
action: "_zed/snooze",
|
||||
} as CreateElicitationResponse),
|
||||
).toEqual({ action: "cancel" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractAskUserQuestions", () => {
|
||||
@@ -190,14 +198,10 @@ describe("askUserQuestionsToCreateRequest", () => {
|
||||
title: "Library",
|
||||
description: undefined,
|
||||
oneOf: [
|
||||
{
|
||||
const: "date-fns",
|
||||
title: "date-fns — Lightweight",
|
||||
// Structured description forwarded under `_meta` so clients can render
|
||||
// it as secondary text instead of parsing it out of the title.
|
||||
_meta: { "_claude/askUserQuestionOption": { description: "Lightweight" } },
|
||||
},
|
||||
// No description → no `_meta` emitted.
|
||||
// The description travels in the enum option's first-class field; the
|
||||
// title stays the clean label (no "label — description" flattening).
|
||||
{ const: "date-fns", title: "date-fns", description: "Lightweight" },
|
||||
// No description → the field is omitted entirely.
|
||||
{ const: "luxon", title: "luxon" },
|
||||
],
|
||||
});
|
||||
@@ -205,7 +209,7 @@ describe("askUserQuestionsToCreateRequest", () => {
|
||||
expect(schema.properties?.["Which library?"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards option description and preview under _meta for rich clients", () => {
|
||||
it("carries descriptions structurally and forwards previews under _meta", () => {
|
||||
const questions = [
|
||||
mkQuestion("Which layout?", [
|
||||
{
|
||||
@@ -226,24 +230,22 @@ describe("askUserQuestionsToCreateRequest", () => {
|
||||
).requestedSchema;
|
||||
const oneOf = (schema.properties?.["question_0"] as { oneOf: EnumOption[] }).oneOf;
|
||||
|
||||
// Both description and preview travel structurally; the title still flattens
|
||||
// the description for clients that only read const/title.
|
||||
// The description uses the enum option's own field; only the preview —
|
||||
// which still has no structural slot — rides under `_meta`.
|
||||
expect(oneOf[0]).toEqual({
|
||||
const: "Grid",
|
||||
title: "Grid — Cards in a responsive grid",
|
||||
title: "Grid",
|
||||
description: "Cards in a responsive grid",
|
||||
_meta: {
|
||||
"_claude/askUserQuestionOption": {
|
||||
description: "Cards in a responsive grid",
|
||||
preview: "```\n[ ] [ ] [ ]\n[ ] [ ] [ ]\n```",
|
||||
},
|
||||
},
|
||||
});
|
||||
// Description only → preview omitted from _meta.
|
||||
expect(oneOf[1]._meta).toEqual({
|
||||
"_claude/askUserQuestionOption": { description: "Stacked rows" },
|
||||
});
|
||||
// Neither → no _meta at all.
|
||||
expect(oneOf[2]._meta).toBeUndefined();
|
||||
// Description only → no _meta needed.
|
||||
expect(oneOf[1]).toEqual({ const: "List", title: "List", description: "Stacked rows" });
|
||||
// Neither → just the label.
|
||||
expect(oneOf[2]).toEqual({ const: "Plain", title: "Plain" });
|
||||
});
|
||||
|
||||
it("includes a per-question optional free-text custom-answer field", () => {
|
||||
@@ -379,6 +381,13 @@ describe("applyAskElicitationResponse", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("returns cancel for a custom/future action it does not understand", () => {
|
||||
const response = { action: "_zed/snooze" } as CreateElicitationResponse;
|
||||
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
|
||||
action: "cancel",
|
||||
});
|
||||
});
|
||||
|
||||
it("omits answers for questions the user left unanswered", () => {
|
||||
const response = {
|
||||
action: "accept",
|
||||
@@ -450,11 +459,14 @@ describe("refusalFallbackToCreateRequest", () => {
|
||||
|
||||
const choice = request.requestedSchema.properties?.choice as {
|
||||
type: string;
|
||||
oneOf: Array<{ const: string; title: string }>;
|
||||
oneOf: Array<{ const: string; title: string; description?: string }>;
|
||||
};
|
||||
expect(choice.type).toBe("string");
|
||||
expect(choice.oneOf.map((o) => o.const)).toEqual(["retry_fallback", "cancelled"]);
|
||||
expect(choice.oneOf[0].title).toContain("claude-opus-4-8");
|
||||
// Each option explains its consequence in the description field.
|
||||
expect(choice.oneOf[0].description).toContain("claude-opus-4-8");
|
||||
expect(choice.oneOf[1].description).toBeTruthy();
|
||||
});
|
||||
|
||||
it("omits the category marker and appends guidance when provided", () => {
|
||||
|
||||
34
src/tests/helpers.ts
Normal file
34
src/tests/helpers.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Shared test doubles. Deliberately vitest-free so `vi.mock` async factories
|
||||
* can `await import("./helpers.js")` without ordering hazards; tests supply
|
||||
* their own vi.fn spies via `overrides`.
|
||||
*/
|
||||
|
||||
/** The context-usage report the base mock query returns. `rawMaxTokens`
|
||||
* matches the agent's DEFAULT_CONTEXT_WINDOW so window-related assertions
|
||||
* don't shift in tests that don't care about context usage. */
|
||||
export const DEFAULT_CONTEXT_USAGE = { totalTokens: 0, rawMaxTokens: 200000 };
|
||||
|
||||
/**
|
||||
* Base stub for the SDK `query()` return object, covering the surface
|
||||
* ClaudeAcpAgent touches unconditionally at session creation. Tests pass
|
||||
* `overrides` for the parts they assert on (spies, custom models, rejecting
|
||||
* getContextUsage, …).
|
||||
*
|
||||
* When the agent starts calling a new SDK method on every session, add it
|
||||
* here once — the getContextUsage adoption required hand-editing ~10 inline
|
||||
* mocks across five files, and any missed copy didn't fail: it silently
|
||||
* rerouted that test through the error-fallback branch and re-polluted test
|
||||
* output.
|
||||
*/
|
||||
export function makeMockQuery(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
initializationResult: async () => ({ models: [] }),
|
||||
setModel: async () => {},
|
||||
setPermissionMode: async () => {},
|
||||
supportedCommands: async () => [],
|
||||
getContextUsage: async () => DEFAULT_CONTEXT_USAGE,
|
||||
[Symbol.asyncIterator]: async function* () {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,11 @@ import { describe, it, expect } from "vitest";
|
||||
import { randomUUID } from "crypto";
|
||||
import { query } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { resolveModelPreference, applyAvailableModelsAllowlist } from "../acp-agent.js";
|
||||
import {
|
||||
resolveModelPreference,
|
||||
applyAvailableModelsAllowlist,
|
||||
matchResumedModel,
|
||||
} from "../acp-agent.js";
|
||||
|
||||
// Mirrors a real `supportedModels()` response: alias rows carry
|
||||
// `resolvedModel`, and "Sonnet 5" has no `major.minor` dot unlike older
|
||||
@@ -68,6 +72,124 @@ describe("resolveModelPreference - resolvedModel matching", () => {
|
||||
it("returns null for a preference that matches nothing", () => {
|
||||
expect(resolveModelPreference(LIVE_SHAPED_MODELS, "claude-gpt-99")).toBeNull();
|
||||
});
|
||||
|
||||
it("matches a '-1m' id-suffix preference against a '[1m]'-spelled resolvedModel", () => {
|
||||
// Without hint canonicalization the exact tier misses and the substring
|
||||
// tier lands on the bare 200k sibling, silently downgrading a 1M pin.
|
||||
const models: ModelInfo[] = [
|
||||
{
|
||||
value: "sonnet",
|
||||
resolvedModel: "claude-sonnet-5",
|
||||
displayName: "Sonnet",
|
||||
description: "Sonnet 5 · Efficient for routine tasks",
|
||||
},
|
||||
{
|
||||
value: "sonnet[1m]",
|
||||
resolvedModel: "claude-sonnet-5[1m]",
|
||||
displayName: "Sonnet",
|
||||
description: "Sonnet 5 with 1M context",
|
||||
},
|
||||
];
|
||||
expect(resolveModelPreference(models, "claude-sonnet-5-1m")?.value).toBe("sonnet[1m]");
|
||||
});
|
||||
|
||||
it("matches a '-1m'-suffix alias directly against a '[1m]'-spelled value", () => {
|
||||
expect(resolveModelPreference(LIVE_SHAPED_MODELS, "opus-1m")?.value).toBe("opus[1m]");
|
||||
});
|
||||
|
||||
// Rows without resolvedModel (older SDKs, allowlist-synthesized entries,
|
||||
// option-derived lists) can't use the exact resolved tier, so the hinted
|
||||
// preference must not be absorbed by the bare sibling in the substring
|
||||
// tier — the tokenized tier scores the hinted row correctly instead.
|
||||
it("does not let a bare sibling absorb a hinted preference when resolvedModel is absent", () => {
|
||||
const models: ModelInfo[] = [
|
||||
{ value: "sonnet", displayName: "Sonnet", description: "" },
|
||||
{ value: "sonnet[1m]", displayName: "Sonnet", description: "" },
|
||||
];
|
||||
expect(resolveModelPreference(models, "claude-sonnet-5[1m]")?.value).toBe("sonnet[1m]");
|
||||
expect(resolveModelPreference(models, "claude-sonnet-5-1m")?.value).toBe("sonnet[1m]");
|
||||
// Bare preferences still land on the bare row via the substring tier.
|
||||
expect(resolveModelPreference(models, "claude-sonnet-5")?.value).toBe("sonnet");
|
||||
});
|
||||
});
|
||||
|
||||
describe("matchResumedModel", () => {
|
||||
// Environment shape from issue #845: the default resolves to Sonnet, the
|
||||
// session was explicitly switched to Opus before the process restart.
|
||||
const SONNET_DEFAULT_MODELS: ModelInfo[] = [
|
||||
{
|
||||
value: "default",
|
||||
resolvedModel: "claude-sonnet-5",
|
||||
displayName: "Default (recommended)",
|
||||
description: "Use the default model (currently Sonnet 5)",
|
||||
},
|
||||
{
|
||||
value: "opus",
|
||||
resolvedModel: "claude-opus-4-6[1m]",
|
||||
displayName: "Opus",
|
||||
description: "Opus 4.6 · Most capable for complex work",
|
||||
},
|
||||
{
|
||||
value: "sonnet",
|
||||
resolvedModel: "claude-sonnet-5",
|
||||
displayName: "Sonnet",
|
||||
description: "Sonnet 5 · Efficient for routine tasks",
|
||||
},
|
||||
{
|
||||
value: "sonnet[1m]",
|
||||
resolvedModel: "claude-sonnet-5[1m]",
|
||||
displayName: "Sonnet",
|
||||
description: "Sonnet 5 with 1M context",
|
||||
},
|
||||
];
|
||||
|
||||
it("maps an explicitly-switched session's live model to its picker alias", () => {
|
||||
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-opus-4-6").value).toBe("opus");
|
||||
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-opus-4-6[1m]").value).toBe("opus");
|
||||
});
|
||||
|
||||
it("keeps Default selected when the live model is what Default resolves to", () => {
|
||||
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-sonnet-5").value).toBe("default");
|
||||
});
|
||||
|
||||
it("prefers an exact named-row resolvedModel match over the hint-stripped Default tier", () => {
|
||||
// "claude-sonnet-5[1m]" differs from Default's "claude-sonnet-5" only by
|
||||
// the context hint; stripping both would misreport the explicit 1M row as
|
||||
// Default (and infer the wrong context window downstream). Both hint
|
||||
// spellings must land on the row regardless of which one it uses.
|
||||
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-sonnet-5[1m]").value).toBe(
|
||||
"sonnet[1m]",
|
||||
);
|
||||
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-sonnet-5-1m").value).toBe("sonnet[1m]");
|
||||
});
|
||||
|
||||
it("normalizes the '-1m' id-suffix hint form and casing against Default's resolution", () => {
|
||||
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-opus-4-8-1m").value).toBe("default");
|
||||
expect(matchResumedModel(LIVE_SHAPED_MODELS, "Claude-Opus-4-8[1M]").value).toBe("default");
|
||||
});
|
||||
|
||||
it("prefers Default over a named row sharing the identical resolvedModel", () => {
|
||||
// "opus[1m]" carries the same resolvedModel as Default; the live id can't
|
||||
// tell the two apart, so a never-customized session stays on Default.
|
||||
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-opus-4-8[1m]").value).toBe("default");
|
||||
});
|
||||
|
||||
it("ignores '[1m]' context hints when comparing against Default's resolution", () => {
|
||||
// The transcript-restored model drops the "[1m]" context hint, so a
|
||||
// session that never left the 1M-context default resumes as the bare id.
|
||||
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-opus-4-8").value).toBe("default");
|
||||
});
|
||||
|
||||
it("still resolves non-default live models against the picker entries", () => {
|
||||
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-sonnet-5").value).toBe("sonnet");
|
||||
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-haiku-4-5-20251001").value).toBe("haiku");
|
||||
});
|
||||
|
||||
it("tracks a live model with no picker counterpart verbatim", () => {
|
||||
const result = matchResumedModel(LIVE_SHAPED_MODELS, "claude-gpt-99");
|
||||
expect(result.value).toBe("claude-gpt-99");
|
||||
expect(result.displayName).toBe("claude-gpt-99");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyAvailableModelsAllowlist - resolvedModel matching", () => {
|
||||
|
||||
@@ -29,6 +29,13 @@ describe("resolvePermissionMode", () => {
|
||||
expect(resolvePermissionMode("bypass")).toBe("bypassPermissions");
|
||||
});
|
||||
|
||||
it("resolves 'manual' as an alias for 'default'", () => {
|
||||
const { logger, error } = mockLogger();
|
||||
expect(resolvePermissionMode("manual", logger)).toBe("default");
|
||||
expect(resolvePermissionMode("Manual", logger)).toBe("default");
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("trims whitespace", () => {
|
||||
expect(resolvePermissionMode(" dontAsk ")).toBe("dontAsk");
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
|
||||
import { makeMockQuery } from "./helpers.js";
|
||||
|
||||
const { registerHookCallbackSpy } = vi.hoisted(() => ({
|
||||
registerHookCallbackSpy: vi.fn(),
|
||||
@@ -101,12 +102,11 @@ describe("session config options", () => {
|
||||
applyFlagSettingsSpy = vi.fn();
|
||||
|
||||
(agent as unknown as { sessions: Record<string, unknown> }).sessions[SESSION_ID] = {
|
||||
query: {
|
||||
query: makeMockQuery({
|
||||
setPermissionMode: setPermissionModeSpy,
|
||||
setModel: setModelSpy,
|
||||
applyFlagSettings: applyFlagSettingsSpy,
|
||||
supportedCommands: async () => [],
|
||||
},
|
||||
}),
|
||||
input: null,
|
||||
cancelled: false,
|
||||
permissionMode: "default",
|
||||
@@ -819,6 +819,85 @@ describe("session config options", () => {
|
||||
expect(setModelSpy).toHaveBeenCalledWith("claude-sonnet-4-6");
|
||||
});
|
||||
|
||||
// Option entries carry no resolvedModel, so alias resolution must consult
|
||||
// session.modelInfos — otherwise a full model id in either hint spelling
|
||||
// ("[1m]"/"-1m") falls to the substring tier and lands on the bare 200k
|
||||
// sibling, silently downgrading the session's context lane.
|
||||
it("resolves a full model id onto its hinted row via session.modelInfos", async () => {
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
session.models = {
|
||||
currentModelId: "sonnet",
|
||||
availableModels: [
|
||||
{ modelId: "sonnet", name: "Sonnet", description: "" },
|
||||
{ modelId: "sonnet[1m]", name: "Sonnet", description: "" },
|
||||
],
|
||||
};
|
||||
session.modelInfos = [
|
||||
{
|
||||
value: "sonnet",
|
||||
resolvedModel: "claude-sonnet-5",
|
||||
displayName: "Sonnet",
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
value: "sonnet[1m]",
|
||||
resolvedModel: "claude-sonnet-5[1m]",
|
||||
displayName: "Sonnet",
|
||||
description: "",
|
||||
},
|
||||
];
|
||||
session.configOptions = session.configOptions.map((o: { id: string }) =>
|
||||
o.id === "model"
|
||||
? {
|
||||
...o,
|
||||
currentValue: "sonnet",
|
||||
options: [
|
||||
{ value: "sonnet", name: "Sonnet" },
|
||||
{ value: "sonnet[1m]", name: "Sonnet" },
|
||||
],
|
||||
}
|
||||
: o,
|
||||
);
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-sonnet-5[1m]",
|
||||
});
|
||||
expect(setModelSpy).toHaveBeenCalledWith("sonnet[1m]");
|
||||
|
||||
setModelSpy.mockClear();
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-sonnet-5-1m",
|
||||
});
|
||||
expect(setModelSpy).toHaveBeenCalledWith("sonnet[1m]");
|
||||
});
|
||||
|
||||
// A session can be running a model with no picker entry (resumed onto a
|
||||
// model excluded by the availableModels allowlist, or a refusal
|
||||
// fallback); its verbatim id is then the option's currentValue. A client
|
||||
// round-tripping that reported value must not get "Invalid value".
|
||||
it("accepts the reported currentValue even when it has no options entry", async () => {
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
session.models = { ...session.models, currentModelId: "claude-offlist-9" };
|
||||
session.configOptions = session.configOptions.map((o: { id: string }) =>
|
||||
o.id === "model" ? { ...o, currentValue: "claude-offlist-9" } : o,
|
||||
);
|
||||
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-offlist-9",
|
||||
});
|
||||
|
||||
expect(setModelSpy).toHaveBeenCalledWith("claude-offlist-9");
|
||||
expect(response.configOptions?.find((o) => o.id === "model")?.currentValue).toBe(
|
||||
"claude-offlist-9",
|
||||
);
|
||||
});
|
||||
|
||||
it("setSessionMode also syncs configOptions", async () => {
|
||||
await agent.setSessionMode({ sessionId: SESSION_ID, modeId: "plan" });
|
||||
|
||||
@@ -848,6 +927,85 @@ describe("session config options", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("context window on model change", () => {
|
||||
beforeEach(() => {
|
||||
populateSession();
|
||||
});
|
||||
|
||||
function getSession() {
|
||||
return (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
}
|
||||
|
||||
it("refreshes contextWindowSize from getContextUsage when the model changes", async () => {
|
||||
const session = getSession();
|
||||
session.query.getContextUsage = vi.fn(async () => ({ rawMaxTokens: 967000 }));
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
expect(session.query.getContextUsage).toHaveBeenCalled();
|
||||
expect(session.contextWindowSize).toBe(967000);
|
||||
});
|
||||
|
||||
it("falls back to resolvedModel text inference when getContextUsage fails", async () => {
|
||||
const session = getSession();
|
||||
session.query.getContextUsage = vi.fn().mockRejectedValue(new Error("boom"));
|
||||
session.modelInfos = session.modelInfos.map((m: ModelInfo) =>
|
||||
m.value === "claude-sonnet-4-6" ? { ...m, resolvedModel: "claude-sonnet-5[1m]" } : m,
|
||||
);
|
||||
// The rejection is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
expect(session.contextWindowSize).toBe(1_000_000);
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the default window when the SDK and inference both miss", async () => {
|
||||
const session = getSession();
|
||||
session.contextWindowSize = 1_000_000;
|
||||
session.query.getContextUsage = vi.fn().mockRejectedValue(new Error("boom"));
|
||||
// The rejection is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
expect(session.contextWindowSize).toBe(200000);
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the learned window when re-asserting the current model", async () => {
|
||||
const session = getSession();
|
||||
session.contextWindowSize = 1_000_000;
|
||||
session.query.getContextUsage = vi.fn(async () => ({ rawMaxTokens: 200000 }));
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-opus-4-5",
|
||||
});
|
||||
|
||||
expect(session.query.getContextUsage).not.toHaveBeenCalled();
|
||||
expect(session.contextWindowSize).toBe(1_000_000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("auto mode availability per model", () => {
|
||||
/**
|
||||
* Augment the session populated by `populateSession()` with a Haiku entry
|
||||
|
||||
@@ -6,9 +6,11 @@ import { AcpClient, ClaudeAcpAgent } from "../acp-agent.js";
|
||||
import { Pushable } from "../utils.js";
|
||||
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
function createMockClient(): AcpClient {
|
||||
function createMockClient(
|
||||
sessionUpdate: (notification: SessionNotification) => Promise<void> = async () => {},
|
||||
): AcpClient {
|
||||
return {
|
||||
sessionUpdate: async (_notification: SessionNotification) => {},
|
||||
sessionUpdate,
|
||||
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
|
||||
readTextFile: async () => ({ content: "" }),
|
||||
writeTextFile: async () => ({}),
|
||||
@@ -330,4 +332,52 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("session load/resume lifecyc
|
||||
}
|
||||
expect(recordedUserChunks.some((c) => c.includes("hi"))).toBe(true);
|
||||
}, 60000);
|
||||
|
||||
// Regression test for https://github.com/agentclientprotocol/claude-agent-acp/issues/845
|
||||
// On resume the CLI restores the model the session's transcript was running,
|
||||
// so loadSession must report that live model instead of recomputing the
|
||||
// env/settings/default choice from scratch. Haiku is used because no account
|
||||
// resolves its default there, so the restored model provably differs from
|
||||
// the freshly-computed default. Requires no ANTHROPIC_MODEL env var and no
|
||||
// "model" key in settings.json (either would pin the model on resume and
|
||||
// mask the reconciliation this test covers).
|
||||
it("ACP: loadSession reports the model the resumed session is actually running", async () => {
|
||||
const agentChunks: string[] = [];
|
||||
const client = createMockClient(async (notification) => {
|
||||
if (notification.update.sessionUpdate === "agent_message_chunk") {
|
||||
const content = notification.update.content;
|
||||
if (content.type === "text") agentChunks.push(content.text);
|
||||
}
|
||||
});
|
||||
|
||||
const modelOf = (opts: { id: string; currentValue?: unknown }[] | null | undefined) =>
|
||||
opts?.find((o) => o.id === "model")?.currentValue;
|
||||
|
||||
const agent = new ClaudeAcpAgent(client);
|
||||
try {
|
||||
const cwd = process.cwd();
|
||||
const { sessionId, configOptions } = await agent.newSession({ cwd, mcpServers: [] });
|
||||
expect(modelOf(configOptions)).not.toBe("haiku");
|
||||
|
||||
await agent.setSessionConfigOption({ sessionId, configId: "model", value: "haiku" });
|
||||
// A real turn on Haiku, so the transcript's last assistant message —
|
||||
// which is what the CLI restores the model from — records it.
|
||||
await agent.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: "text", text: "Reply with only the word OK" }],
|
||||
});
|
||||
await agent.closeSession({ sessionId });
|
||||
|
||||
const loadResp = await agent.loadSession({ sessionId, cwd, mcpServers: [] });
|
||||
expect(modelOf(loadResp.configOptions)).toBe("haiku");
|
||||
|
||||
// And the reported option agrees with the live session (`/context`).
|
||||
agentChunks.length = 0;
|
||||
await agent.prompt({ sessionId, prompt: [{ type: "text", text: "/context" }] });
|
||||
const liveModel = agentChunks.join("\n").match(/\*\*Model:\*\*\s*(\S+)/)?.[1];
|
||||
expect(liveModel).toContain("haiku");
|
||||
} finally {
|
||||
await agent.dispose();
|
||||
}
|
||||
}, 120000);
|
||||
});
|
||||
|
||||
@@ -2153,3 +2153,512 @@ describe("createTaskHook", () => {
|
||||
expect(changes).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("empty message content is not emitted", () => {
|
||||
const mockClient = {} as AcpClient;
|
||||
const mockLogger: Logger = { log: () => {}, error: () => {} };
|
||||
|
||||
it("drops an empty string content instead of emitting an empty agent_message_chunk", () => {
|
||||
const notifications = toAcpNotifications(
|
||||
"",
|
||||
"assistant",
|
||||
"test-session",
|
||||
{},
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
expect(notifications).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("still emits non-empty string content", () => {
|
||||
const notifications = toAcpNotifications(
|
||||
"hello",
|
||||
"assistant",
|
||||
"test-session",
|
||||
{},
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
expect(notifications).toHaveLength(1);
|
||||
expect(notifications[0].update).toMatchObject({
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "hello" },
|
||||
});
|
||||
});
|
||||
|
||||
it("drops an empty streamed text block but keeps a non-empty one", () => {
|
||||
const notifications = toAcpNotifications(
|
||||
[
|
||||
{ type: "text", text: "" },
|
||||
{ type: "text", text: "real" },
|
||||
] as any,
|
||||
"assistant",
|
||||
"test-session",
|
||||
{},
|
||||
mockClient,
|
||||
mockLogger,
|
||||
);
|
||||
expect(notifications).toHaveLength(1);
|
||||
expect(notifications[0].update).toMatchObject({
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "real" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Agent/Task tool_result rendering from tool_use_result", () => {
|
||||
const mockClient = {} as AcpClient;
|
||||
const mockLogger: Logger = { log: () => {}, error: () => {} };
|
||||
|
||||
const TRAILER =
|
||||
"\nagentId: a0e1eff08fcb6e2e8 (use SendMessage with to: 'a0e1eff08fcb6e2e8', summary: '<5-10 word recap>' to continue this agent)\n<usage>subagent_tokens: 11735\ntool_uses: 2\nduration_ms: 21237</usage>";
|
||||
|
||||
const agentToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_agent",
|
||||
name: "Task",
|
||||
input: { description: "Explore", prompt: "look around" },
|
||||
};
|
||||
|
||||
const rawResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_agent",
|
||||
content: [{ type: "text", text: `The report.${TRAILER}` }],
|
||||
};
|
||||
|
||||
const structured = {
|
||||
status: "completed",
|
||||
agentId: "a0e1eff08fcb6e2e8",
|
||||
content: [{ type: "text", text: "The structured report." }],
|
||||
totalTokens: 11735,
|
||||
totalToolUseCount: 2,
|
||||
totalDurationMs: 21237,
|
||||
};
|
||||
|
||||
it("renders the structured subagent report instead of the raw trailer text", () => {
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, structured);
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [{ type: "content", content: { type: "text", text: "The structured report." } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("strips the trailer from the raw fallback when tool_use_result is absent", () => {
|
||||
// Replayed sessions and older CLIs have no structured report; the
|
||||
// tail-anchored strip is the only cleanup available there.
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false);
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [{ type: "content", content: { type: "text", text: "The report." } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("strips only matching trailer parts and leaves unrecognized text alone", () => {
|
||||
const oddResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_agent",
|
||||
content: [
|
||||
{ type: "text", text: "Report A.\n<usage>subagent_tokens: 5</usage>" },
|
||||
{ type: "text", text: "Report B.\nagentId: abc-123 (for resuming)" },
|
||||
{ type: "text", text: "agentId mentioned mid-text (not a trailer) stays.\nDone." },
|
||||
],
|
||||
};
|
||||
const update = toolUpdateFromToolResult(oddResult, agentToolUse, false);
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: "Report A." } },
|
||||
{ type: "content", content: { type: "text", text: "Report B." } },
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "agentId mentioned mid-text (not a trailer) stays.\nDone." },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back (trailer-stripped) when tool_use_result is the async_launched variant", () => {
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, {
|
||||
status: "async_launched",
|
||||
agentId: "a0e1eff08fcb6e2e8",
|
||||
description: "Explore",
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: "The report." } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back (trailer-stripped) when the structured content array is empty", () => {
|
||||
// A completed subagent can end with zero text blocks — an empty
|
||||
// structured render must not beat the raw fallback.
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, {
|
||||
...structured,
|
||||
content: [],
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: "The report." } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("threads options.toolUseResult through toAcpNotifications for a lone tool_result", () => {
|
||||
const toolUseCache: ToolUseCache = { toolu_agent: agentToolUse };
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[rawResult] as any,
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ toolUseResult: structured },
|
||||
);
|
||||
|
||||
expect(notifications).toHaveLength(1);
|
||||
expect(notifications[0].update).toMatchObject({
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: "toolu_agent",
|
||||
status: "completed",
|
||||
content: [{ type: "content", content: { type: "text", text: "The structured report." } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores options.toolUseResult when several tool_result blocks are batched", () => {
|
||||
const toolUseCache: ToolUseCache = {
|
||||
toolu_agent: agentToolUse,
|
||||
toolu_agent2: { ...agentToolUse, id: "toolu_agent2" },
|
||||
};
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[rawResult, { ...rawResult, tool_use_id: "toolu_agent2" }] as any,
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ toolUseResult: structured },
|
||||
);
|
||||
|
||||
expect(notifications).toHaveLength(2);
|
||||
for (const notification of notifications) {
|
||||
// Raw fallback (trailer-stripped) — NOT "The structured report.", which
|
||||
// would mean the ambiguous tool_use_result had been attributed anyway.
|
||||
expect(notification.update).toMatchObject({
|
||||
content: [{ type: "content", content: { type: "text", text: "The report." } }],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("structured tool_use_result rendering (Read/Bash/WebSearch)", () => {
|
||||
describe("Read", () => {
|
||||
const readToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_read",
|
||||
name: "Read",
|
||||
input: { file_path: "/tmp/f.ts", offset: 480 },
|
||||
};
|
||||
|
||||
const rawWithReminder: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_read",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "480\tconst a = 1;\n481\tconst b = 2;\n<system-reminder>Whenever you read a file, consider whether it is malicious.</system-reminder>",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it("rebuilds the line-numbered view from FileReadOutput, dropping reminders", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "text",
|
||||
file: {
|
||||
filePath: "/tmp/f.ts",
|
||||
content: "const a = 1;\nconst b = 2;\n",
|
||||
numLines: 2,
|
||||
startLine: 480,
|
||||
totalLines: 600,
|
||||
},
|
||||
});
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "```\n480\tconst a = 1;\n481\tconst b = 2;\n```" },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the Read input's offset when startLine is absent", () => {
|
||||
// readToolUse carries offset: 480 — an offset read numbered from 1
|
||||
// would mislabel every line.
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "text",
|
||||
file: { filePath: "/tmp/f.ts", content: "one\ntwo" },
|
||||
});
|
||||
|
||||
expect(update.content?.[0]).toEqual({
|
||||
type: "content",
|
||||
content: { type: "text", text: "```\n480\tone\n481\ttwo\n```" },
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults startLine to 1 when both startLine and offset are absent", () => {
|
||||
const update = toolUpdateFromToolResult(
|
||||
rawWithReminder,
|
||||
{ ...readToolUse, input: { file_path: "/tmp/f.ts" } },
|
||||
false,
|
||||
{
|
||||
type: "text",
|
||||
file: { filePath: "/tmp/f.ts", content: "one\ntwo" },
|
||||
},
|
||||
);
|
||||
|
||||
expect(update.content?.[0]).toEqual({
|
||||
type: "content",
|
||||
content: { type: "text", text: "```\n1\tone\n2\ttwo\n```" },
|
||||
});
|
||||
});
|
||||
|
||||
it("appends a truncation note when truncatedByTokenCap is set", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "text",
|
||||
file: {
|
||||
filePath: "/tmp/f.ts",
|
||||
content: "one\ntwo\n",
|
||||
numLines: 2,
|
||||
startLine: 1,
|
||||
totalLines: 9000,
|
||||
truncatedByTokenCap: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(update.content?.[0]).toEqual({
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "```\n1\tone\n2\ttwo\n[File truncated: showing 2 of 9000 lines]\n```",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to raw content for non-text variants", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "image",
|
||||
file: { base64: "aGk=", type: "image/png", originalSize: 3 },
|
||||
});
|
||||
|
||||
// Raw path: markdown-escaped raw text (reminder included — image reads
|
||||
// don't carry reminders in practice).
|
||||
expect(update.content).toHaveLength(1);
|
||||
expect((update.content?.[0] as any).content.text).toContain("const a = 1;");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bash", () => {
|
||||
const bashToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_bash",
|
||||
name: "Bash",
|
||||
input: { command: "git push" },
|
||||
};
|
||||
|
||||
const HINT =
|
||||
"\n[This command modified 1 file you've previously read: src/foo.ts. Call Read before editing.]";
|
||||
|
||||
const rawWithHint: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_bash",
|
||||
content: `pushed ok${HINT}`,
|
||||
};
|
||||
|
||||
const structured = {
|
||||
stdout: "pushed ok",
|
||||
stderr: "",
|
||||
interrupted: false,
|
||||
isImage: false,
|
||||
};
|
||||
|
||||
it("prefers structured stdout/stderr over raw text with model-directed hints", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, structured);
|
||||
|
||||
expect(update._meta?.terminal_output).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
data: "pushed ok",
|
||||
});
|
||||
expect(update._meta?.terminal_exit).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
exit_code: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("joins stderr after stdout like the code-execution path", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, false, {
|
||||
...structured,
|
||||
stderr: "warning: something",
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "```console\npushed ok\nwarning: something\n```" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to raw text for backgrounded commands", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, {
|
||||
...structured,
|
||||
stdout: "",
|
||||
backgroundTaskId: "bash_1",
|
||||
});
|
||||
|
||||
expect(update._meta?.terminal_output?.data).toBe(`pushed ok${HINT}`);
|
||||
});
|
||||
|
||||
it("falls back to the raw content array for image output", () => {
|
||||
const imageResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_bash",
|
||||
content: [
|
||||
{ type: "image", source: { type: "base64", data: "aGk=", media_type: "image/png" } },
|
||||
],
|
||||
};
|
||||
const update = toolUpdateFromToolResult(imageResult, bashToolUse, true, {
|
||||
...structured,
|
||||
isImage: true,
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "image", data: "aGk=", mimeType: "image/png" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("re-establishes the abort notice and a failing exit code for interrupted commands", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, {
|
||||
...structured,
|
||||
stdout: "partial output",
|
||||
interrupted: true,
|
||||
});
|
||||
|
||||
expect(update._meta?.terminal_output).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
data: "partial output\n[Command was aborted before completion]",
|
||||
});
|
||||
expect(update._meta?.terminal_exit).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
exit_code: 1,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("re-establishes the truncation note and persisted path for too-large outputs", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, {
|
||||
...structured,
|
||||
stdout: "clipped stdout",
|
||||
persistedOutputPath: "/tmp/tool-results/abc.txt",
|
||||
persistedOutputSize: 38100,
|
||||
});
|
||||
|
||||
expect(update._meta?.terminal_output).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
data: "clipped stdout\n[Output truncated (38100 bytes total): full output saved to /tmp/tool-results/abc.txt]",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("WebSearch", () => {
|
||||
const searchToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_search",
|
||||
name: "WebSearch",
|
||||
input: { query: "npm sigstore bug" },
|
||||
};
|
||||
|
||||
const rawDump: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_search",
|
||||
content:
|
||||
'Web search results for query: "npm sigstore bug"\n\nLinks: [{"title":"Issue #9722","url":"https://github.com/npm/cli/issues/9722"}]',
|
||||
};
|
||||
|
||||
it("renders hits as Title (url) lines from WebSearchOutput", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false, {
|
||||
query: "npm sigstore bug",
|
||||
durationSeconds: 5.5,
|
||||
results: [
|
||||
"I found one relevant issue:",
|
||||
{
|
||||
tool_use_id: "srvtoolu_1",
|
||||
content: [
|
||||
{ title: "Issue #9722", url: "https://github.com/npm/cli/issues/9722" },
|
||||
{ title: "sigstore-js", url: "https://github.com/sigstore/sigstore-js" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "I found one relevant issue:\nIssue #9722 (https://github.com/npm/cli/issues/9722)\nsigstore-js (https://github.com/sigstore/sigstore-js)",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the raw dump when tool_use_result is absent", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false);
|
||||
|
||||
expect((update.content?.[0] as any).content.text).toContain("Web search results for query");
|
||||
});
|
||||
|
||||
it("skips off-spec hits instead of rendering undefined fields", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false, {
|
||||
query: "npm sigstore bug",
|
||||
durationSeconds: 5.5,
|
||||
results: [
|
||||
{
|
||||
tool_use_id: "srvtoolu_1",
|
||||
content: [
|
||||
{ error_code: "provider_error" },
|
||||
{ title: "Issue #9722", url: "https://github.com/npm/cli/issues/9722" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "Issue #9722 (https://github.com/npm/cli/issues/9722)",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the raw dump when every hit is off-spec", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false, {
|
||||
query: "npm sigstore bug",
|
||||
durationSeconds: 5.5,
|
||||
results: [{ tool_use_id: "srvtoolu_1", content: [{ error_code: "provider_error" }] }],
|
||||
});
|
||||
|
||||
expect((update.content?.[0] as any).content.text).toContain("Web search results for query");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
248
src/tools.ts
248
src/tools.ts
@@ -8,10 +8,13 @@ import {
|
||||
import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
AgentInput,
|
||||
AgentOutput,
|
||||
AskUserQuestionInput,
|
||||
BashInput,
|
||||
BashOutput,
|
||||
FileEditInput,
|
||||
FileReadInput,
|
||||
FileReadOutput,
|
||||
FileWriteInput,
|
||||
GlobInput,
|
||||
GrepInput,
|
||||
@@ -22,6 +25,7 @@ import {
|
||||
TodoWriteInput,
|
||||
WebFetchInput,
|
||||
WebSearchInput,
|
||||
WebSearchOutput,
|
||||
} from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
|
||||
import {
|
||||
ImageBlockParam,
|
||||
@@ -486,6 +490,55 @@ export function toolInfoFromToolUse(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow the untyped message-level `tool_use_result` toward a per-tool Output
|
||||
* shape: rejects everything but a plain non-null object (arrays pass a bare
|
||||
* `typeof === "object"` check, so they're excluded here). The returned value
|
||||
* is only *nominally* typed — it arrives over the wire from arbitrary CLI
|
||||
* versions, so each caller must still guard the specific fields it reads
|
||||
* before trusting them.
|
||||
*/
|
||||
function structuredResult<T extends object>(toolUseResult: unknown): T | undefined {
|
||||
return toolUseResult !== null &&
|
||||
typeof toolUseResult === "object" &&
|
||||
!Array.isArray(toolUseResult)
|
||||
? (toolUseResult as T)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the model-directed trailer from a raw Agent/Task tool_result text:
|
||||
* a `<usage>…</usage>` totals block and/or an
|
||||
* `agentId: <id> (use SendMessage …)` continuation line at the end of the
|
||||
* text. Both patterns are tail-anchored and independent (older CLIs emit
|
||||
* variants with only one of them), so a format change makes them stop
|
||||
* matching rather than mangle the report.
|
||||
*/
|
||||
function stripAgentTrailer(text: string): string {
|
||||
return text
|
||||
.replace(/\n?<usage>[\s\S]*?<\/usage>\s*$/, "")
|
||||
.replace(/\n?agentId: [\w-]+ \([^)]*\)\s*$/, "");
|
||||
}
|
||||
|
||||
/** Apply {@link stripAgentTrailer} across a raw tool_result `content` (plain
|
||||
* string or block array), leaving non-text blocks untouched. */
|
||||
function stripAgentTrailerFromContent(content: unknown): unknown {
|
||||
if (typeof content === "string") {
|
||||
return stripAgentTrailer(content);
|
||||
}
|
||||
if (Array.isArray(content)) {
|
||||
return content.map((block) =>
|
||||
block !== null &&
|
||||
typeof block === "object" &&
|
||||
block.type === "text" &&
|
||||
typeof block.text === "string"
|
||||
? { ...block, text: stripAgentTrailer(block.text) }
|
||||
: block,
|
||||
);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
export function toolUpdateFromToolResult(
|
||||
toolResult:
|
||||
| ToolResultBlockParam
|
||||
@@ -500,6 +553,7 @@ export function toolUpdateFromToolResult(
|
||||
| BetaToolSearchToolResultBlockParam,
|
||||
toolUse: any | undefined,
|
||||
supportsTerminalOutput: boolean = false,
|
||||
toolUseResult?: unknown,
|
||||
): ToolUpdate {
|
||||
if (
|
||||
"is_error" in toolResult &&
|
||||
@@ -512,8 +566,63 @@ export function toolUpdateFromToolResult(
|
||||
return toAcpContentUpdate(toolResult.content, true);
|
||||
}
|
||||
|
||||
// Shared raw-text fallback: renders the tool_result content the model saw.
|
||||
// The structured cases below fall back to this when `tool_use_result` is
|
||||
// absent or fails its shape guard (older CLIs, replayed sessions).
|
||||
const rawContentUpdate = () =>
|
||||
toAcpContentUpdate(toolResult.content, "is_error" in toolResult ? toolResult.is_error : false);
|
||||
|
||||
switch (toolUse?.name) {
|
||||
case "Read":
|
||||
case "Read": {
|
||||
// The raw tool_result text is the model-facing view: line-numbered
|
||||
// content plus any appended <system-reminder> blocks (malicious-code
|
||||
// checks, memory staleness notes, …) that clients shouldn't see. The
|
||||
// structured FileReadOutput carries the clean content — rebuild the
|
||||
// line-numbered view from it. Non-text variants (image/notebook/pdf)
|
||||
// fall back to the raw content blocks, which already render fine.
|
||||
const structuredRead = structuredResult<FileReadOutput>(toolUseResult);
|
||||
if (
|
||||
structuredRead?.type === "text" &&
|
||||
typeof structuredRead.file?.content === "string" &&
|
||||
// An empty file has nothing to line-number; keep the raw view (the
|
||||
// model-facing "file is empty" note) rather than a phantom blank line.
|
||||
structuredRead.file.content.length > 0
|
||||
) {
|
||||
// startLine is typed non-optional but defended anyway; a Read's
|
||||
// `offset` input is the same 1-based starting line, so it beats a
|
||||
// blind 1 when an emitter omits the field.
|
||||
const startLine =
|
||||
structuredRead.file.startLine ??
|
||||
(toolUse.input as FileReadInput | undefined)?.offset ??
|
||||
1;
|
||||
// A trailing newline is a line terminator, not an extra line — don't
|
||||
// number a phantom empty line after it.
|
||||
let numbered = structuredRead.file.content
|
||||
.replace(/\n$/, "")
|
||||
.split("\n")
|
||||
.map((line, i) => `${startLine + i}\t${line}`)
|
||||
.join("\n");
|
||||
// The model-facing truncation banner doesn't survive reconstruction
|
||||
// from file.content (the SDK flag exists for exactly this case) —
|
||||
// re-establish it so a partial first page doesn't read as the whole
|
||||
// file.
|
||||
if (structuredRead.file.truncatedByTokenCap) {
|
||||
const { numLines, totalLines } = structuredRead.file;
|
||||
const detail =
|
||||
typeof numLines === "number" && typeof totalLines === "number"
|
||||
? `: showing ${numLines} of ${totalLines} lines`
|
||||
: "";
|
||||
numbered += `\n[File truncated${detail}]`;
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: markdownEscape(numbered) },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
|
||||
return {
|
||||
content: toolResult.content.map((content: any) => ({
|
||||
@@ -541,6 +650,7 @@ export function toolUpdateFromToolResult(
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
case "Bash": {
|
||||
const result = toolResult.content;
|
||||
@@ -548,15 +658,57 @@ export function toolUpdateFromToolResult(
|
||||
const isError = "is_error" in toolResult && toolResult.is_error;
|
||||
|
||||
// Extract output and exit code from either format:
|
||||
// 1. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
|
||||
// 2. Plain string content from a regular tool_result
|
||||
// 3. Array content (e.g. [{ type: "text", text: "..." }] for stdout,
|
||||
// 1. The structured BashOutput (message-level tool_use_result): its
|
||||
// stdout/stderr exclude the model-directed suffixes the raw text
|
||||
// carries (stale-read hints, gh rate-limit hints, the
|
||||
// persisted-output wrapper for too-large outputs — the interruption
|
||||
// and truncation facts those carried are re-established from the
|
||||
// structured flags below). Skipped for image output (the raw content
|
||||
// array carries the actual image blocks) and backgrounded commands
|
||||
// (the raw text carries the background-task notice; structured
|
||||
// stdout may be empty).
|
||||
// 2. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
|
||||
// 3. Plain string content from a regular tool_result
|
||||
// 4. Array content (e.g. [{ type: "text", text: "..." }] for stdout,
|
||||
// or [{ type: "image", source: {...} }] when the local Bash tool
|
||||
// produces an image, e.g. piping a base64 data URI)
|
||||
let output = "";
|
||||
let exitCode = isError ? 1 : 0;
|
||||
|
||||
const structuredBash = structuredResult<BashOutput>(toolUseResult);
|
||||
if (
|
||||
structuredBash &&
|
||||
typeof structuredBash.stdout === "string" &&
|
||||
typeof structuredBash.stderr === "string" &&
|
||||
!structuredBash.isImage &&
|
||||
structuredBash.backgroundTaskId === undefined
|
||||
) {
|
||||
output = [structuredBash.stdout, structuredBash.stderr].filter(Boolean).join("\n");
|
||||
// Two raw-text notices don't survive the structured stdout/stderr —
|
||||
// re-establish them so the client isn't shown a clean-looking result:
|
||||
// the CLI appends its abort marker only to the model-facing text, and
|
||||
// an aborted command isn't a success, so synthesize a failing exit
|
||||
// code when the result wasn't already an error.
|
||||
if (structuredBash.interrupted) {
|
||||
output = [output, "[Command was aborted before completion]"].filter(Boolean).join("\n");
|
||||
exitCode = 1;
|
||||
}
|
||||
// Structured stdout is clipped (~30k chars) when the full output was
|
||||
// persisted to disk; without this note the clip is silent and the
|
||||
// path to the full output is lost.
|
||||
if (typeof structuredBash.persistedOutputPath === "string") {
|
||||
const size =
|
||||
typeof structuredBash.persistedOutputSize === "number"
|
||||
? ` (${structuredBash.persistedOutputSize} bytes total)`
|
||||
: "";
|
||||
output = [
|
||||
output,
|
||||
`[Output truncated${size}: full output saved to ${structuredBash.persistedOutputPath}]`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
} else if (
|
||||
result &&
|
||||
typeof result === "object" &&
|
||||
"type" in result &&
|
||||
@@ -619,6 +771,45 @@ export function toolUpdateFromToolResult(
|
||||
return {};
|
||||
}
|
||||
|
||||
case "Agent":
|
||||
case "Task": {
|
||||
// The raw tool_result text ends with a model-directed trailer (an
|
||||
// `agentId: … (use SendMessage …)` line plus a `<usage>` totals block)
|
||||
// that ACP clients shouldn't see. The message-level `tool_use_result`
|
||||
// carries the structured AgentOutput whose `content` is the subagent's
|
||||
// report without the trailer — render from it when present (per the SDK
|
||||
// 0.3.207 guidance) and fall back to the raw text otherwise (older CLIs,
|
||||
// replayed sessions).
|
||||
// Narrowed to the full union, not the completed variant — the status
|
||||
// check below is what discriminates it, and pre-narrowing would let
|
||||
// future field reads typecheck against a variant the runtime value may
|
||||
// not be.
|
||||
const structured = structuredResult<AgentOutput>(toolUseResult);
|
||||
if (
|
||||
structured?.status === "completed" &&
|
||||
Array.isArray(structured.content) &&
|
||||
// A completed subagent can end with zero text blocks; an empty
|
||||
// structured render would beat the raw fallback for no benefit.
|
||||
structured.content.length > 0
|
||||
) {
|
||||
return toAcpContentUpdate(
|
||||
structured.content,
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
}
|
||||
// No structured report to render from (replayed sessions —
|
||||
// getSessionMessages doesn't expose the transcript's toolUseResult —
|
||||
// and older CLIs). The SDK advises rendering from tool_use_result
|
||||
// instead of parsing the text, but with no structured value the
|
||||
// tail-anchored strip is the only cleanup available; if the trailer
|
||||
// format changes it simply stops matching and the full raw text
|
||||
// renders, no worse than before.
|
||||
return toAcpContentUpdate(
|
||||
stripAgentTrailerFromContent(toolResult.content),
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
}
|
||||
|
||||
case "Edit": // Edit is handled in hooks
|
||||
case "Write": {
|
||||
return {};
|
||||
@@ -628,15 +819,54 @@ export function toolUpdateFromToolResult(
|
||||
return { title: "Exited Plan Mode" };
|
||||
}
|
||||
|
||||
case "WebSearch": {
|
||||
// The raw tool_result text is a model-directed dump ("Web search
|
||||
// results for query: …\n\nLinks: [{…json…}]"). The structured
|
||||
// WebSearchOutput carries the hits — render them the way server-side
|
||||
// web_search_result blocks render ("Title (url)").
|
||||
const structuredSearch = structuredResult<WebSearchOutput>(toolUseResult);
|
||||
if (structuredSearch && Array.isArray(structuredSearch.results)) {
|
||||
const lines = structuredSearch.results.flatMap((entry) =>
|
||||
typeof entry === "string"
|
||||
? [entry]
|
||||
: Array.isArray(entry?.content)
|
||||
? // tool_use_result arrives untyped across CLI version skew —
|
||||
// skip off-spec hits rather than rendering
|
||||
// "undefined (undefined)" lines.
|
||||
entry.content.flatMap((hit) =>
|
||||
typeof hit?.title === "string" && typeof hit?.url === "string"
|
||||
? [formatWebSearchHit(hit)]
|
||||
: [],
|
||||
)
|
||||
: [],
|
||||
);
|
||||
if (lines.length > 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: lines.join("\n") },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
return rawContentUpdate();
|
||||
}
|
||||
|
||||
default: {
|
||||
return toAcpContentUpdate(
|
||||
toolResult.content,
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
return rawContentUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** One display format for a web-search hit, shared by the structured
|
||||
* WebSearchOutput render and the server-side `web_search_result` block so
|
||||
* the two paths can't drift. */
|
||||
function formatWebSearchHit(hit: { title: string; url: string }): string {
|
||||
return `${hit.title} (${hit.url})`;
|
||||
}
|
||||
|
||||
function toAcpContentUpdate(
|
||||
content: any,
|
||||
isError: boolean = false,
|
||||
@@ -711,7 +941,7 @@ function toAcpContentBlock(content: ToolResultContent, isError: boolean): Conten
|
||||
`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`,
|
||||
);
|
||||
case "web_search_result":
|
||||
return wrapText(`${content.title} (${content.url})`);
|
||||
return wrapText(formatWebSearchHit(content));
|
||||
case "web_search_tool_result_error":
|
||||
return wrapText(`Error: ${content.error_code}`);
|
||||
case "web_fetch_result":
|
||||
|
||||
Reference in New Issue
Block a user