mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-05 08:51:16 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea7076c0bc | ||
|
|
b4e3aaa727 | ||
|
|
d3eff19157 | ||
|
|
a04d35496c | ||
|
|
9aaf0663bf |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.71.0"
|
||||
".": "0.73.0"
|
||||
}
|
||||
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,5 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## [0.73.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.72.0...v0.73.0) (2026-09-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** update Claude SDK ([#1066](https://github.com/agentclientprotocol/claude-agent-acp/issues/1066)) ([b4e3aaa](https://github.com/agentclientprotocol/claude-agent-acp/commit/b4e3aaa727821a85cef6dcf51452381d35fd68a5))
|
||||
|
||||
## [0.72.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.71.0...v0.72.0) (2026-09-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** Update to @anthropic-ai/claude-agent-sdk 0.3.252 ([#1062](https://github.com/agentclientprotocol/claude-agent-acp/issues/1062)) ([9aaf066](https://github.com/agentclientprotocol/claude-agent-acp/commit/9aaf0663bf48a4209e8e6511af395102c05e501e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Adopt per-model effort settings and user_message_uuid result attribution ([#1065](https://github.com/agentclientprotocol/claude-agent-acp/issues/1065)) ([a04d354](https://github.com/agentclientprotocol/claude-agent-acp/commit/a04d35496c1f1b4c19d8f4204ee6377de45624ab))
|
||||
|
||||
## [0.71.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.70.0...v0.71.0) (2026-08-31)
|
||||
|
||||
|
||||
|
||||
459
package-lock.json
generated
459
package-lock.json
generated
@@ -1,31 +1,31 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.71.0",
|
||||
"version": "0.73.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.71.0",
|
||||
"version": "0.73.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.3.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.238",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
"@agentclientprotocol/sdk": "1.4.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.257",
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.120.0",
|
||||
"@anthropic-ai/sdk": "0.122.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.67.0",
|
||||
"@typescript-eslint/parser": "8.67.0",
|
||||
"eslint": "10.8.1",
|
||||
"@types/node": "26.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.69.0",
|
||||
"@typescript-eslint/parser": "8.69.0",
|
||||
"eslint": "10.9.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.11.0",
|
||||
"globals": "17.12.0",
|
||||
"prettier": "3.9.6",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
@@ -36,31 +36,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.3.0.tgz",
|
||||
"integrity": "sha512-i3h/efaeuMUFAO1HSfo97QZQnnvMd7wWBYtBsdL6UMZg3a78sk3Ffya5Xu7C7tYsXomXoDXJBAzQF2PcFKAhIQ==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.4.0.tgz",
|
||||
"integrity": "sha512-/eufudw+aFY1LKLolT6yFE6UMmYRl7fMJ/DEONSIyR6wI3slHWITBsANRGqXEY8FRzqUxwh7QEaGiZHcJPVThg==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.238.tgz",
|
||||
"integrity": "sha512-ppRfbAflZuV7HPqr8BkHPEk8c7gih3PK+GHnZq6zP0Uo7bJHXAJwc4Za8LJRm4hXMixBNMjK1cU/VWfQ9fE2sg==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.257.tgz",
|
||||
"integrity": "sha512-Se55zXv48IYLg/WzoXzpbPLcq86suwDSbRUoNb69l4dkovorqS/47Xuy7MUo/gPNwwcPB4a+aqbXbshU33dcdQ==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.238",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.238",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.238",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.238",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.238",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.238",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.238",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.238"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.257",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.257",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.257",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.257",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.257",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.257",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.257",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.257"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -69,9 +69,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.238.tgz",
|
||||
"integrity": "sha512-7KctNItTzHRiDg+jFxTM46o+Y/YS52V6HSopaWPggO6BbR+3MV/rKMmq+zP93If7eVwadW9Yg9vHLjXDKIw9OQ==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.257.tgz",
|
||||
"integrity": "sha512-ITjFPYB8riu9tbxbrWArokiZ/90w/NDrYbtEvyr3ScilVtu1iupkComxkhvbUxoyT4JRDpKsdw/ZOfwSl/pkpA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -82,9 +82,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.238.tgz",
|
||||
"integrity": "sha512-aDt8LXjWISwLzqeCBHxJC5AR1iVYlY5UYIm2VLztjI1U0PCb1BiH/IfyyjjizqRhhEwyQp9YAJeZZq8n/2vQEA==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.257.tgz",
|
||||
"integrity": "sha512-0s7QoLRnopbMvCqVpgCnvBWg4UNxPUybMZTknkn3HLBMvUjUdHs1QXb77c/yrT1WAPqDTw1Ju+H0esXwWa8/Kg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -95,9 +95,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.238.tgz",
|
||||
"integrity": "sha512-P7V9TFokcNRdIJPUzDQ0GLBfMuoewWEH4rh6U3e7RaEAxzdeUcdS9P0N4arXqK2YAOtK1o93QSEtiGUF1fTfWw==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.257.tgz",
|
||||
"integrity": "sha512-38tv1s1CaIE6CyEBmJpfKtvwPzrasxPiRT079BEs5aaiLPQ+pmSGF0+Lwy7ot9i6xqr7v5/91TVo5JXHI8Pkpg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -108,9 +108,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.238.tgz",
|
||||
"integrity": "sha512-ankSEMAMTVulKYg0NT8fZ7a5+q+aIzfiqhcrLe9zYPeuhE+lpNeq0JPMcHyOtUAENxVI0/2J2q5OXGl9O0qWcg==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.257.tgz",
|
||||
"integrity": "sha512-t2WHfKjY4Jjgzfk0lbBUt6EVPQe+wXvXvRmYnK7ICfzZ1jS8CrpDajwBQErKFSMiUqGAutZy3vwDwGziW32cpw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -121,9 +121,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.238.tgz",
|
||||
"integrity": "sha512-/arAOSqtIAWDu7Z4Uf2Un3n+/5Zg3NpxZKzMQEDnYNkbJRhr44sM59TFaEOiWq1FqlUJKi6wHjgm+TW5B8DWWg==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.257.tgz",
|
||||
"integrity": "sha512-0FRyIwV4jEJErdDDoYMC0v9lzuFNz5y0lK2340H5fP02eNXsP3U0htKW/bfRP8Ppei+xc4QUZgdCI6rVzkhXGg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -134,9 +134,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.238.tgz",
|
||||
"integrity": "sha512-buo3IBSd7EmYcQsH+OKARgNNbSF1+l/+z1hDXSXGKzwD1LFioJZ/k9FWWWC166SoQ93jPmN+G/ksTzEUB3zRGQ==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.257.tgz",
|
||||
"integrity": "sha512-qSlgAUEpj2JAA+YqMD222iv2W3x8xBDSYwdoaOx20VbaJRjFq8feViI7kcr1C6wVN+ApX4Rxl7YX6gekiBkA2g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -147,9 +147,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.238.tgz",
|
||||
"integrity": "sha512-yW8lhV7QgYiNu3NatjgmkhUspgJsG2N2N6lmm/7B99sWFobKTbVLMLsXxj6A46R6qE2C4Zzm6PjxgGaMYOMn0g==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.257.tgz",
|
||||
"integrity": "sha512-7iEwy14lSqaAWvNR3KucBusQs5+hG6uoliYWZ2M2FyaCk5PHYymVf9mrRPtWhaYfNwum/YY13acYIIvnAbz0kA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -160,9 +160,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.238",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.238.tgz",
|
||||
"integrity": "sha512-6Fb2JRrBci282fBfhCGB0Tpq7N8V5HMMqsO3YGFkc9hhbn/y9G7glNKSNgRtxD5Ujjxj8rhiaYstXW2DJzanzw==",
|
||||
"version": "0.3.257",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.257.tgz",
|
||||
"integrity": "sha512-NW0zMjHXFBdu2TcjT9Zo5o/1tJaDy6A7v4Gt/vLVJgaipV/RzAbyRGXZzH37hswyiKrSLGaSoYYP1vUfncbkUQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -173,9 +173,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.120.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.120.0.tgz",
|
||||
"integrity": "sha512-ZlvmNFT/iIF6JD13rxbbMWD8nvGR0RaUp6yMQnoc+4Af0YjVVe/bIdW1XSQQsoxXAtg1NaT6Vak0LKFlJ4d37Q==",
|
||||
"version": "0.122.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.122.0.tgz",
|
||||
"integrity": "sha512-GGPNftt0caaz9MDlmNQGHX8855Ojaduyy5pm9Sm1h7HalCn0cWNb5/bweadJF+4yzbal+QL6ztBa09WAAOzLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
@@ -420,9 +420,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
|
||||
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -479,9 +479,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.146.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz",
|
||||
"integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==",
|
||||
"version": "0.147.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz",
|
||||
"integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -489,9 +489,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm-eabi": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz",
|
||||
"integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz",
|
||||
"integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -506,9 +506,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz",
|
||||
"integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz",
|
||||
"integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -523,9 +523,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz",
|
||||
"integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz",
|
||||
"integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -540,9 +540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz",
|
||||
"integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz",
|
||||
"integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -557,9 +557,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz",
|
||||
"integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz",
|
||||
"integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -574,9 +574,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz",
|
||||
"integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz",
|
||||
"integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -591,9 +591,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz",
|
||||
"integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz",
|
||||
"integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -608,9 +608,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz",
|
||||
"integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz",
|
||||
"integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -625,9 +625,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz",
|
||||
"integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz",
|
||||
"integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -642,9 +642,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz",
|
||||
"integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz",
|
||||
"integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -659,9 +659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz",
|
||||
"integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz",
|
||||
"integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -676,9 +676,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz",
|
||||
"integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz",
|
||||
"integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -693,9 +693,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz",
|
||||
"integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz",
|
||||
"integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -710,9 +710,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz",
|
||||
"integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz",
|
||||
"integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -727,9 +727,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz",
|
||||
"integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz",
|
||||
"integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -838,9 +838,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
|
||||
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
|
||||
"version": "26.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz",
|
||||
"integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -848,17 +848,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz",
|
||||
"integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.69.0.tgz",
|
||||
"integrity": "sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.67.0",
|
||||
"@typescript-eslint/type-utils": "8.67.0",
|
||||
"@typescript-eslint/utils": "8.67.0",
|
||||
"@typescript-eslint/visitor-keys": "8.67.0",
|
||||
"@typescript-eslint/scope-manager": "8.69.0",
|
||||
"@typescript-eslint/type-utils": "8.69.0",
|
||||
"@typescript-eslint/utils": "8.69.0",
|
||||
"@typescript-eslint/visitor-keys": "8.69.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -871,22 +871,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
"@typescript-eslint/parser": "^8.69.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.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz",
|
||||
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.69.0.tgz",
|
||||
"integrity": "sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.67.0",
|
||||
"@typescript-eslint/types": "8.67.0",
|
||||
"@typescript-eslint/typescript-estree": "8.67.0",
|
||||
"@typescript-eslint/visitor-keys": "8.67.0",
|
||||
"@typescript-eslint/scope-manager": "8.69.0",
|
||||
"@typescript-eslint/types": "8.69.0",
|
||||
"@typescript-eslint/typescript-estree": "8.69.0",
|
||||
"@typescript-eslint/visitor-keys": "8.69.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -902,14 +902,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz",
|
||||
"integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.69.0.tgz",
|
||||
"integrity": "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.67.0",
|
||||
"@typescript-eslint/types": "^8.67.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.69.0",
|
||||
"@typescript-eslint/types": "^8.69.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -924,14 +924,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz",
|
||||
"integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.69.0.tgz",
|
||||
"integrity": "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.67.0",
|
||||
"@typescript-eslint/visitor-keys": "8.67.0"
|
||||
"@typescript-eslint/types": "8.69.0",
|
||||
"@typescript-eslint/visitor-keys": "8.69.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -942,9 +942,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz",
|
||||
"integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.69.0.tgz",
|
||||
"integrity": "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -959,15 +959,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz",
|
||||
"integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.69.0.tgz",
|
||||
"integrity": "sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.67.0",
|
||||
"@typescript-eslint/typescript-estree": "8.67.0",
|
||||
"@typescript-eslint/utils": "8.67.0",
|
||||
"@typescript-eslint/types": "8.69.0",
|
||||
"@typescript-eslint/typescript-estree": "8.69.0",
|
||||
"@typescript-eslint/utils": "8.69.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -984,9 +984,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz",
|
||||
"integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.69.0.tgz",
|
||||
"integrity": "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -998,16 +998,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz",
|
||||
"integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.69.0.tgz",
|
||||
"integrity": "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.67.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.67.0",
|
||||
"@typescript-eslint/types": "8.67.0",
|
||||
"@typescript-eslint/visitor-keys": "8.67.0",
|
||||
"@typescript-eslint/project-service": "8.69.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.69.0",
|
||||
"@typescript-eslint/types": "8.69.0",
|
||||
"@typescript-eslint/visitor-keys": "8.69.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1026,16 +1026,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz",
|
||||
"integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.69.0.tgz",
|
||||
"integrity": "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.67.0",
|
||||
"@typescript-eslint/types": "8.67.0",
|
||||
"@typescript-eslint/typescript-estree": "8.67.0"
|
||||
"@typescript-eslint/scope-manager": "8.69.0",
|
||||
"@typescript-eslint/types": "8.69.0",
|
||||
"@typescript-eslint/typescript-estree": "8.69.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1050,13 +1050,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz",
|
||||
"integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==",
|
||||
"version": "8.69.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.69.0.tgz",
|
||||
"integrity": "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.67.0",
|
||||
"@typescript-eslint/types": "8.69.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1645,9 +1645,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.8.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz",
|
||||
"integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==",
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.1.tgz",
|
||||
"integrity": "sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -1973,9 +1973,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz",
|
||||
"integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==",
|
||||
"version": "8.7.0",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz",
|
||||
"integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -2019,9 +2019,9 @@
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz",
|
||||
"integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2224,9 +2224,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.11.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz",
|
||||
"integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==",
|
||||
"version": "17.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz",
|
||||
"integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2276,9 +2276,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.13.2",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.2.tgz",
|
||||
"integrity": "sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA==",
|
||||
"version": "4.13.5",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.5.tgz",
|
||||
"integrity": "sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -2324,9 +2324,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz",
|
||||
"integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==",
|
||||
"version": "7.0.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz",
|
||||
"integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2351,9 +2351,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz",
|
||||
"integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==",
|
||||
"version": "10.7.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz",
|
||||
"integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -2407,9 +2407,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.2.9",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.9.tgz",
|
||||
"integrity": "sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==",
|
||||
"version": "6.2.10",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.10.tgz",
|
||||
"integrity": "sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
@@ -2888,13 +2888,34 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz",
|
||||
"integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"content-type": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator/node_modules/content-type": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
|
||||
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
@@ -3062,9 +3083,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
||||
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3164,9 +3185,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
|
||||
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -3221,13 +3242,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz",
|
||||
"integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz",
|
||||
"integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.146.0",
|
||||
"@oxc-project/types": "=0.147.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3237,21 +3258,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.5",
|
||||
"@rolldown/binding-android-arm64": "1.2.5",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.5",
|
||||
"@rolldown/binding-darwin-x64": "1.2.5",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.5",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.5",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.5",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.5",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.5",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.5",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.5",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.5",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.5",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.5",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.5"
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.6",
|
||||
"@rolldown/binding-android-arm64": "1.2.6",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.6",
|
||||
"@rolldown/binding-darwin-x64": "1.2.6",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.6",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.6",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.6",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.6",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.6",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.6",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.6",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.6",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.6",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.6",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
@@ -3467,9 +3488,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/standardwebhooks": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
|
||||
"integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz",
|
||||
"integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stablelib/base64": "^1.0.0",
|
||||
@@ -3955,9 +3976,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
|
||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz",
|
||||
"integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
18
package.json
18
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.71.0",
|
||||
"version": "0.73.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -65,20 +65,20 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.3.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.238",
|
||||
"@agentclientprotocol/sdk": "1.4.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.257",
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.120.0",
|
||||
"@anthropic-ai/sdk": "0.122.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.67.0",
|
||||
"@typescript-eslint/parser": "8.67.0",
|
||||
"eslint": "10.8.1",
|
||||
"@types/node": "26.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.69.0",
|
||||
"@typescript-eslint/parser": "8.69.0",
|
||||
"eslint": "10.9.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.11.0",
|
||||
"globals": "17.12.0",
|
||||
"prettier": "3.9.6",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
|
||||
335
src/acp-agent.ts
335
src/acp-agent.ts
@@ -80,6 +80,7 @@ import {
|
||||
SDKMessageOrigin,
|
||||
SDKPartialAssistantMessage,
|
||||
SDKUserMessage,
|
||||
Settings,
|
||||
SlashCommand,
|
||||
ThinkingConfig,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
@@ -705,6 +706,14 @@ export type Session = {
|
||||
* user's intent so it persists across model switches; the Fast mode config
|
||||
* option is only surfaced while the selected model supports it. */
|
||||
fastModeEnabled: boolean;
|
||||
/** Whether the user picked a non-default effort through the ACP picker this
|
||||
* session. A pin lives at the SDK's flag layer, which overrides the CLI's
|
||||
* persisted effort (including the per-model `modelSettings` entries), so it
|
||||
* follows the session across model switches; without one, the CLI resolves
|
||||
* effort itself and the Effort option is display-only. Cleared when the
|
||||
* user picks "Default" (the flag layer is cleared with it) or when a model
|
||||
* switch clamps the pin away. */
|
||||
effortPinnedByUser?: boolean;
|
||||
/** Why the SDK currently can't serve Fast mode, when the reason is one worth
|
||||
* telling the user about (see {@link FAST_MODE_UNAVAILABLE_EXPLANATIONS} —
|
||||
* routine states like the SDK's own opt-in requirement normalize to
|
||||
@@ -1475,11 +1484,11 @@ export interface AcpClient {
|
||||
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
|
||||
/** `signal`, when aborted, sends `$/cancel_request` for the in-flight
|
||||
* elicitation so the client can dismiss its prompt and settle our await. */
|
||||
unstable_createElicitation(
|
||||
createElicitation(
|
||||
params: CreateElicitationRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CreateElicitationResponse>;
|
||||
unstable_completeElicitation(params: CompleteElicitationNotification): Promise<void>;
|
||||
completeElicitation(params: CompleteElicitationNotification): Promise<void>;
|
||||
/** Send a custom (extension) notification, e.g. `_claude/sdkMessage`. */
|
||||
extNotification(method: string, params: Record<string, unknown>): Promise<void>;
|
||||
}
|
||||
@@ -1514,7 +1523,7 @@ class ClientConnection implements AcpClient {
|
||||
return this.ctx.request(methods.client.fs.writeTextFile, params);
|
||||
}
|
||||
|
||||
unstable_createElicitation(
|
||||
createElicitation(
|
||||
params: CreateElicitationRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CreateElicitationResponse> {
|
||||
@@ -1523,7 +1532,7 @@ class ClientConnection implements AcpClient {
|
||||
});
|
||||
}
|
||||
|
||||
unstable_completeElicitation(params: CompleteElicitationNotification): Promise<void> {
|
||||
completeElicitation(params: CompleteElicitationNotification): Promise<void> {
|
||||
return this.ctx.notify(methods.client.elicitation.complete, params);
|
||||
}
|
||||
|
||||
@@ -1652,7 +1661,7 @@ async function authenticateMcpServer(
|
||||
serverName,
|
||||
flowAbort.signal,
|
||||
);
|
||||
const elicitation = host.client.unstable_createElicitation(
|
||||
const elicitation = host.client.createElicitation(
|
||||
{
|
||||
mode: "url",
|
||||
sessionId,
|
||||
@@ -1675,7 +1684,7 @@ async function authenticateMcpServer(
|
||||
await completed;
|
||||
}
|
||||
try {
|
||||
await host.client.unstable_completeElicitation({ elicitationId });
|
||||
await host.client.completeElicitation({ elicitationId });
|
||||
} catch (error) {
|
||||
if (!flowAbort.signal.aborted) {
|
||||
host.logger.error(`Failed to complete MCP OAuth elicitation: ${error}`);
|
||||
@@ -2767,12 +2776,20 @@ export class ClaudeAcpAgent {
|
||||
*
|
||||
* But an echo-less result can also be an ORPHAN: cancel() settles+removes a
|
||||
* queued turn whose user message was already pushed, so the SDK still runs
|
||||
* it and emits a result with no uuid to match. Promoting the head for an
|
||||
* it and emits a result with no echo to match. Promoting the head for an
|
||||
* orphan would misattribute its stop reason/usage to an unrelated later
|
||||
* prompt. `session.pendingOrphanResults` counts exactly how many such
|
||||
* orphans are still expected (FIFO, they arrive before any live turn's
|
||||
* result), so we skip those and only promote once the count is drained. */
|
||||
const ensureActiveTurn = () => {
|
||||
* result), so we skip those and only promote once the count is drained.
|
||||
*
|
||||
* `resultUserMessageUuid` is the result's own join key (SDK 0.3.246+
|
||||
* echoes the triggering send's client uuid on results; absent on older
|
||||
* CLIs, synthetic/meta turns, and session-scoped failures). When present
|
||||
* it upgrades the map lane from positional heuristics to an exact match:
|
||||
* a stamp naming an orphaned command consumes the result outright, and a
|
||||
* stamp naming anything else positively refutes "this is a dead turn's
|
||||
* result", so the dup-over-loss one-skip must not eat it. */
|
||||
const ensureActiveTurn = (resultUserMessageUuid?: string) => {
|
||||
if (session.activeTurn) {
|
||||
if (!isHeldOpen(session.activeTurn)) {
|
||||
return;
|
||||
@@ -2824,8 +2841,13 @@ export class ClaudeAcpAgent {
|
||||
// double-consume it. The unexpected-transition logging in the frame
|
||||
// handler is the tripwire for that class of drift.
|
||||
if (session.orphanCommands?.size) {
|
||||
const stampedOrphan =
|
||||
resultUserMessageUuid !== undefined && session.orphanCommands.has(resultUserMessageUuid);
|
||||
let consumedOrphanResult = false;
|
||||
let oldestPending: string | undefined;
|
||||
// The started/zombie drain applies regardless of the stamp: commands
|
||||
// folded into the turn that emitted this result share it, and zombies'
|
||||
// late results have already passed (or never existed).
|
||||
for (const [uuid, state] of session.orphanCommands) {
|
||||
if (state === "started" || state === "zombie") {
|
||||
consumedOrphanResult = true;
|
||||
@@ -2834,17 +2856,32 @@ export class ClaudeAcpAgent {
|
||||
oldestPending ??= uuid;
|
||||
}
|
||||
}
|
||||
if (consumedOrphanResult) {
|
||||
if (stampedOrphan) {
|
||||
// Exact join: the result names an orphaned command. Delete the
|
||||
// matched entry even when it is still "pending" (its dispatch frame
|
||||
// was lost) and consume the result — no promotion.
|
||||
session.orphanCommands.delete(resultUserMessageUuid!);
|
||||
return;
|
||||
}
|
||||
if (oldestPending !== undefined) {
|
||||
// No dispatch was seen before this result, so it is very likely a
|
||||
// live turn's — but a lost "started" frame would mean it IS the
|
||||
// orphan's (dup-over-loss: prefer one wrong skip over
|
||||
// misattributing a dead turn's outcome to a live prompt). Grant
|
||||
// each pending entry exactly one skip, like the count lane did.
|
||||
session.orphanCommands.delete(oldestPending);
|
||||
return;
|
||||
if (resultUserMessageUuid !== undefined) {
|
||||
// The stamp names a send that is NOT in the orphan map, so this is
|
||||
// a live turn's result: skip both the consumed-return (its folded
|
||||
// orphans were drained above, but the result itself still needs a
|
||||
// turn) and the dup-over-loss one-skip the stamp refutes, and fall
|
||||
// through to promote the head.
|
||||
} else {
|
||||
if (consumedOrphanResult) {
|
||||
return;
|
||||
}
|
||||
if (oldestPending !== undefined) {
|
||||
// No dispatch was seen before this result, so it is very likely a
|
||||
// live turn's — but a lost "started" frame would mean it IS the
|
||||
// orphan's (dup-over-loss: prefer one wrong skip over
|
||||
// misattributing a dead turn's outcome to a live prompt). Grant
|
||||
// each pending entry exactly one skip, like the count lane did.
|
||||
session.orphanCommands.delete(oldestPending);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const head = firstUnsettledQueuedTurn();
|
||||
@@ -3858,7 +3895,7 @@ export class ClaudeAcpAgent {
|
||||
// client supports url elicitation; ignore failures otherwise.
|
||||
if (this.clientCapabilities?.elicitation?.url) {
|
||||
try {
|
||||
await this.client.unstable_completeElicitation({
|
||||
await this.client.completeElicitation({
|
||||
elicitationId: message.elicitation_id,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -3950,7 +3987,7 @@ export class ClaudeAcpAgent {
|
||||
});
|
||||
}
|
||||
if (persistent) {
|
||||
await this.syncModelAfterRefusalFallback(
|
||||
await this.syncModelAfterExternalSwitch(
|
||||
params.sessionId,
|
||||
session,
|
||||
message.fallback_model,
|
||||
@@ -4065,7 +4102,7 @@ export class ClaudeAcpAgent {
|
||||
// the map in that case).
|
||||
if (!isAutonomousResult) {
|
||||
recordResultForOrphanCommands();
|
||||
ensureActiveTurn();
|
||||
ensureActiveTurn(message.user_message_uuid);
|
||||
// Once the submitted goal command has produced its own result,
|
||||
// no older runtime update can still precede it in the ordered
|
||||
// SDK stream. Stop suppressing updates even when this runtime
|
||||
@@ -4289,13 +4326,28 @@ export class ClaudeAcpAgent {
|
||||
isEmptyUserInterruptionDiagnostic(message) &&
|
||||
(session.pendingEmptyInterruptionDiagnosticCommands?.size ?? 0) > 0
|
||||
) {
|
||||
const commandUuid = session
|
||||
.pendingEmptyInterruptionDiagnosticCommands!.values()
|
||||
.next().value;
|
||||
if (commandUuid) {
|
||||
session.pendingEmptyInterruptionDiagnosticCommands!.delete(commandUuid);
|
||||
const pending = session.pendingEmptyInterruptionDiagnosticCommands!;
|
||||
const stamped = message.user_message_uuid;
|
||||
if (stamped === undefined) {
|
||||
// Older producer: no join key on the result, so consume an
|
||||
// arbitrary hand-off token like before.
|
||||
const commandUuid = pending.values().next().value;
|
||||
if (commandUuid) {
|
||||
pending.delete(commandUuid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
if (pending.delete(stamped)) {
|
||||
// Exact join (SDK 0.3.246+ echoes the triggering send's
|
||||
// uuid on error results): the diagnostic names a cancelled
|
||||
// command we handed a token for — swallow it.
|
||||
break;
|
||||
}
|
||||
// Stamped but unmatched: this diagnostic belongs to a send we
|
||||
// did NOT cancel — a live turn's real failure. Don't let a
|
||||
// stale token eat it; fall through to the ordinary failure
|
||||
// lanes (the clear below retires the stale tokens, same as
|
||||
// every other non-diagnostic result path).
|
||||
}
|
||||
|
||||
// Some CLI versions omit the cancelled cycle's diagnostic. Do
|
||||
@@ -5287,8 +5339,10 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
// Each removed queued turn's user message was already pushed to the SDK,
|
||||
// which processes input FIFO and will still emit a result for it with no
|
||||
// uuid to match. Track those so the consumer skips them (see
|
||||
// ensureActiveTurn) rather than misattributing them to the head.
|
||||
// user echo to match (0.3.246+ CLIs do stamp results with the
|
||||
// triggering send's user_message_uuid, which ensureActiveTurn uses as
|
||||
// an exact join when present). Track those so the consumer skips them
|
||||
// (see ensureActiveTurn) rather than misattributing them to the head.
|
||||
// msg_lifecycle_v1 CLIs get per-uuid tracking drained by the command's
|
||||
// own terminal lifecycle frame — exact under command coalescing, where
|
||||
// N queued commands fold into ONE turn emitting one result and a plain
|
||||
@@ -6376,7 +6430,7 @@ export class ClaudeAcpAgent {
|
||||
|
||||
try {
|
||||
const response = await this.withPendingUserInput(sessionId, () =>
|
||||
this.client.unstable_createElicitation(createRequest, signal),
|
||||
this.client.createElicitation(createRequest, signal),
|
||||
);
|
||||
if (signal.aborted) {
|
||||
return { action: "cancel" };
|
||||
@@ -6414,7 +6468,7 @@ export class ClaudeAcpAgent {
|
||||
let response;
|
||||
try {
|
||||
response = await this.withPendingUserInput(sessionId, () =>
|
||||
this.client.unstable_createElicitation(createRequest, signal),
|
||||
this.client.createElicitation(createRequest, signal),
|
||||
);
|
||||
} catch (error) {
|
||||
// A cancellation we requested (signal aborted) settles as an aborted tool
|
||||
@@ -6459,10 +6513,7 @@ export class ClaudeAcpAgent {
|
||||
let response: CreateElicitationResponse;
|
||||
try {
|
||||
response = await this.withPendingUserInput(sessionId, () =>
|
||||
this.client.unstable_createElicitation(
|
||||
refusalFallbackToCreateRequest(prompt, sessionId),
|
||||
signal,
|
||||
),
|
||||
this.client.createElicitation(refusalFallbackToCreateRequest(prompt, sessionId), signal),
|
||||
);
|
||||
} catch (error) {
|
||||
// A cancellation we requested (signal aborted) is expected teardown;
|
||||
@@ -6556,15 +6607,26 @@ export class ClaudeAcpAgent {
|
||||
session.fastModeDisabledReason = undefined;
|
||||
}
|
||||
|
||||
// Rebuild config options since effort levels depend on the selected model
|
||||
// Rebuild config options since effort levels depend on the selected
|
||||
// model. The effort seed depends on who owns the choice: a user pin
|
||||
// made through the ACP picker this session lives at the SDK's flag
|
||||
// layer and follows the session across switches, so carry it forward.
|
||||
// Otherwise the CLI resolves effort itself from the persisted settings
|
||||
// — the NEW model's `modelSettings` entry first (the CLI persists
|
||||
// /effort per model), then the legacy top-level value — so display
|
||||
// what it will actually run instead of dragging the old model's value
|
||||
// along.
|
||||
const effortOpt = session.configOptions.find((o) => o.id === EFFORT_CONFIG_ID);
|
||||
const currentEffort =
|
||||
typeof effortOpt?.currentValue === "string" ? effortOpt.currentValue : undefined;
|
||||
const seedEffort = session.effortPinnedByUser
|
||||
? currentEffort
|
||||
: settingsEffortForModel(session.settingsManager.getSettings(), newModelInfo, value);
|
||||
session.configOptions = buildConfigOptions(
|
||||
session.modes,
|
||||
session.models,
|
||||
session.modelInfos,
|
||||
currentEffort,
|
||||
seedEffort,
|
||||
session.agents,
|
||||
session.currentAgent,
|
||||
{
|
||||
@@ -6578,14 +6640,25 @@ export class ClaudeAcpAgent {
|
||||
},
|
||||
);
|
||||
|
||||
// Sync effort with the SDK if it changed after the model switch
|
||||
const newEffortOpt = session.configOptions.find((o) => o.id === EFFORT_CONFIG_ID);
|
||||
const newEffort =
|
||||
typeof newEffortOpt?.currentValue === "string" ? newEffortOpt.currentValue : undefined;
|
||||
if (newEffort !== currentEffort) {
|
||||
await session.query.applyFlagSettings({
|
||||
effortLevel: toSdkEffortLevel(newEffort),
|
||||
});
|
||||
// Sync effort with the SDK only when a user pin changed across the
|
||||
// switch — i.e. the new model clamped it away (buildConfigOptions
|
||||
// validated the seed against the new model's levels), where the flag
|
||||
// must be cleared too or the SDK would keep running the old pin
|
||||
// invisibly. Settings-derived seeds are display-only: the CLI resolves
|
||||
// persisted effort itself, and pinning it at the flag layer would
|
||||
// shadow the per-model values on every later switch.
|
||||
if (session.effortPinnedByUser) {
|
||||
const newEffortOpt = session.configOptions.find((o) => o.id === EFFORT_CONFIG_ID);
|
||||
const newEffort =
|
||||
typeof newEffortOpt?.currentValue === "string" ? newEffortOpt.currentValue : undefined;
|
||||
if (newEffort !== currentEffort) {
|
||||
await session.query.applyFlagSettings({
|
||||
effortLevel: toSdkEffortLevel(newEffort),
|
||||
});
|
||||
if (newEffort === undefined || newEffort === "default") {
|
||||
session.effortPinnedByUser = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Emit current_mode_update only after session.modes AND
|
||||
@@ -6618,42 +6691,48 @@ export class ClaudeAcpAgent {
|
||||
await session.query.applyFlagSettings({
|
||||
effortLevel: toSdkEffortLevel(value),
|
||||
});
|
||||
// "Default" clears the flag layer (toSdkEffortLevel → null), handing
|
||||
// effort back to the CLI's persisted per-model resolution — so it
|
||||
// un-pins; any other pick pins effort for the session.
|
||||
session.effortPinnedByUser = value !== "default";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reconcile adapter model state after the SDK persistently swapped the
|
||||
* session's model out from under us (refusal fallback). The SDK already
|
||||
* made the switch, so this must NOT call `query.setModel` — it only
|
||||
* updates our bookkeeping (currentModelId, context window, mode clamping,
|
||||
* effort/Fast-mode options) via the same `applyConfigOptionValue` path a
|
||||
* user-driven model change takes, then notifies the client. */
|
||||
private async syncModelAfterRefusalFallback(
|
||||
/** Reconcile adapter model state after the SDK switched the session's
|
||||
* model out from under us — a refusal fallback, or any switch reported by
|
||||
* the PostModelSwitch hook that the adapter didn't drive (e.g. a `/model`
|
||||
* command typed as a prompt). The SDK already made the switch, so this
|
||||
* must NOT call `query.setModel` — it only updates our bookkeeping
|
||||
* (currentModelId, context window, mode clamping, effort/Fast-mode
|
||||
* options) via the same `applyConfigOptionValue` path a user-driven model
|
||||
* change takes, then notifies the client. */
|
||||
private async syncModelAfterExternalSwitch(
|
||||
sessionId: string,
|
||||
session: Session,
|
||||
fallbackModel: string,
|
||||
switchedModel: string,
|
||||
): Promise<void> {
|
||||
// Map the SDK-reported model onto one of the session's model options
|
||||
// (handles display names and `resolvedModel` ids). The fallback model may
|
||||
// not be among the options — e.g. excluded by the user's
|
||||
// (handles display names and `resolvedModel` ids). The switched-to model
|
||||
// may not be among the options — e.g. excluded by the user's
|
||||
// `availableModels` allowlist — in which case we track the raw id: the
|
||||
// picker shows no selection, but the model-dependent bookkeeping and any
|
||||
// later `setModel` round-trip stay truthful to what the SDK is running.
|
||||
const resolved = resolveModelPreference(session.modelInfos, fallbackModel);
|
||||
const value = resolved?.value ?? fallbackModel;
|
||||
const resolved = resolveModelPreference(session.modelInfos, switchedModel);
|
||||
const value = resolved?.value ?? switchedModel;
|
||||
if (session.models.currentModelId === value) return;
|
||||
|
||||
try {
|
||||
await this.updateConfigOption(sessionId, MODEL_CONFIG_ID, value);
|
||||
} catch (err) {
|
||||
// This runs on the consumer loop: a throw here tears down the query
|
||||
// stream (failAllTurns + closeQueryStream) and bricks the session —
|
||||
// far worse than stale bookkeeping. The user-driven RPC path lets the
|
||||
// same errors propagate to fail just that request; here we log and
|
||||
// move on, matching the setPermissionMode containment inside
|
||||
// applyConfigOptionValue.
|
||||
// This runs on the consumer loop (or detached from a hook callback): a
|
||||
// throw here tears down the query stream (failAllTurns +
|
||||
// closeQueryStream) and bricks the session — far worse than stale
|
||||
// bookkeeping. The user-driven RPC path lets the same errors propagate
|
||||
// to fail just that request; here we log and move on, matching the
|
||||
// setPermissionMode containment inside applyConfigOptionValue.
|
||||
this.logger.error(
|
||||
`Failed to reconcile model state after refusal fallback to "${fallbackModel}":`,
|
||||
`Failed to reconcile model state after external switch to "${switchedModel}":`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
@@ -7113,6 +7192,44 @@ export class ClaudeAcpAgent {
|
||||
],
|
||||
},
|
||||
],
|
||||
// Mirror model switches the adapter didn't drive into the ACP picker
|
||||
// and the model-dependent bookkeeping (context window, mode clamping,
|
||||
// effort/Fast-mode options). Without this, a `/model <name>` command
|
||||
// typed as a prompt — which the CLI executes as a local command —
|
||||
// switches the session's model with no refusal-fallback frame, and
|
||||
// the client's picker silently drifts. `source: 'sdk'` is the
|
||||
// adapter's own setModel (applyConfigOptionValue already synced it),
|
||||
// and 'resume' restores are read back via readResumedLiveModel, so
|
||||
// only the remaining sources sync here (CLI 2.1.251+; older CLIs
|
||||
// never fire the hook and keep the pre-hook behavior).
|
||||
PostModelSwitch: [
|
||||
...(userProvidedOptions?.hooks?.PostModelSwitch || []),
|
||||
{
|
||||
hooks: [
|
||||
async (input) => {
|
||||
if (
|
||||
input.hook_event_name === "PostModelSwitch" &&
|
||||
input.source !== "sdk" &&
|
||||
input.source !== "resume"
|
||||
) {
|
||||
// Don't run the sync before answering the hook: the sync
|
||||
// can issue applyFlagSettings (an outgoing control request)
|
||||
// while the CLI is awaiting this hook's response, and SDK
|
||||
// control requests are serialized over one channel.
|
||||
// syncModelAfterExternalSwitch contains its own errors, so
|
||||
// the detached call can't surface an unhandled rejection.
|
||||
const toModel = input.to_model;
|
||||
setImmediate(() => {
|
||||
const live = this.sessions[sessionId];
|
||||
if (!live) return;
|
||||
void this.syncModelAfterExternalSwitch(sessionId, live, toModel);
|
||||
});
|
||||
}
|
||||
return { continue: true };
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
...(fileChangeAuditSupport
|
||||
? {
|
||||
Stop: [
|
||||
@@ -7241,7 +7358,7 @@ export class ClaudeAcpAgent {
|
||||
// (current and future); the identity fields are overridden because the
|
||||
// fuzzy-matched sibling's resolvedModel/displayName/description can
|
||||
// describe a different context lane and would poison later resolvedModel
|
||||
// matching (syncModelAfterRefusalFallback) and context-window inference
|
||||
// matching (syncModelAfterExternalSwitch) and context-window inference
|
||||
// (applyConfigOptionValue) if they traveled under this id.
|
||||
const modelInfos = fallbackModelInfo
|
||||
? [
|
||||
@@ -7295,27 +7412,23 @@ export class ClaudeAcpAgent {
|
||||
disabledReason: fastModeDisabledReason,
|
||||
};
|
||||
|
||||
// The Effort picker seed mirrors what the CLI itself resolves from the
|
||||
// persisted settings — the current model's `modelSettings` entry first
|
||||
// (the CLI persists /effort per model), then the legacy top-level value.
|
||||
// Display-only: no applyFlagSettings here. The CLI reads the same
|
||||
// settings, so pinning the seed at the flag layer was always redundant —
|
||||
// and it would override the CLI's own per-model restore on every later
|
||||
// model switch. Only a user's explicit ACP picker choice pins the flag
|
||||
// (see applyConfigOptionValue / Session.effortPinnedByUser).
|
||||
const configOptions = buildConfigOptions(
|
||||
modes,
|
||||
models,
|
||||
modelInfos,
|
||||
settingsManager.getSettings().effortLevel,
|
||||
settingsEffortForModel(settingsManager.getSettings(), currentModelInfo),
|
||||
agents,
|
||||
currentAgent,
|
||||
fastMode,
|
||||
);
|
||||
|
||||
// Apply the initial effort level to the SDK so it matches the UI default
|
||||
const initialEffort = configOptions.find((o) => o.id === EFFORT_CONFIG_ID);
|
||||
if (
|
||||
initialEffort &&
|
||||
typeof initialEffort.currentValue === "string" &&
|
||||
initialEffort.currentValue !== "default"
|
||||
) {
|
||||
await q.applyFlagSettings({
|
||||
effortLevel: toSdkEffortLevel(initialEffort.currentValue),
|
||||
});
|
||||
}
|
||||
// Seed the context window WITHOUT any extra IPC on the session/new path.
|
||||
// On session/load, the resumed session's own `getContextUsage` report — a
|
||||
// response `getAvailableModels` already awaited to learn the live model
|
||||
@@ -7757,6 +7870,32 @@ function toSdkEffortLevel(value: string | undefined): EffortLevel | null {
|
||||
return value === undefined || value === "default" ? null : (value as EffortLevel);
|
||||
}
|
||||
|
||||
/** The effort level the CLI itself resolves for a model from the persisted
|
||||
* settings: the per-model entry (`modelSettings`, keyed by canonical model
|
||||
* name — the CLI persists /effort per model since 2.1.243) wins over the
|
||||
* legacy top-level `effortLevel`. Alias picker rows are looked up by their
|
||||
* resolved model id first, then the row value, then the raw config value
|
||||
* for models not in the picker. Exact keys only — the CLI's canonical-form
|
||||
* fallback matching isn't replicated, so a miss simply falls back to the
|
||||
* top-level value (best-effort display; `buildConfigOptions` still
|
||||
* validates the result against the model's supported levels). */
|
||||
export function settingsEffortForModel(
|
||||
settings: Settings,
|
||||
modelInfo: ModelInfo | undefined,
|
||||
modelId?: string,
|
||||
): string | undefined {
|
||||
const modelSettings = settings.modelSettings;
|
||||
if (modelSettings) {
|
||||
for (const key of [modelInfo?.resolvedModel, modelInfo?.value, modelId]) {
|
||||
const perModel = key !== undefined ? modelSettings[key]?.effortLevel : undefined;
|
||||
if (typeof perModel === "string") {
|
||||
return perModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return settings.effortLevel;
|
||||
}
|
||||
|
||||
// `supportedAgents()` always returns Claude Code's built-in subagents — the
|
||||
// ones used for Task-tool delegation (Explore, Plan, etc.) — even when the user
|
||||
// has configured none of their own. Those aren't meaningful *main-thread*
|
||||
@@ -8195,7 +8334,7 @@ export function resolveModelPreference(models: ModelInfo[], preference: string):
|
||||
* 4. `resolveModelPreference` over the picker entries.
|
||||
* 5. A model with no picker counterpart (e.g. excluded by an
|
||||
* `availableModels` allowlist) is tracked verbatim, mirroring
|
||||
* `syncModelAfterRefusalFallback`: the picker shows no selection, but the
|
||||
* `syncModelAfterExternalSwitch`: the picker shows no selection, but the
|
||||
* model-dependent bookkeeping stays truthful to what the SDK is running. */
|
||||
export function matchResumedModel(models: ModelInfo[], liveModel: string): ModelInfo {
|
||||
const live = canonicalizeModelId(liveModel);
|
||||
@@ -8335,6 +8474,15 @@ export function applyAvailableModelsAllowlist(
|
||||
* #596). Best-effort: a control-request failure is logged and returns nulls
|
||||
* so callers keep their current choice; failing the whole session/load over
|
||||
* an unreadable report would be worse. */
|
||||
/** Whether a rejected `setModel` was vetoed by a user-configured
|
||||
* PreModelSwitch hook. The CLI's rejection message is the stable,
|
||||
* distinguishable marker ("Model switch blocked by a PreModelSwitch hook:
|
||||
* <reason>"); a format change makes this stop matching and the failure
|
||||
* falls back to the ordinary fail-loud path, no worse than before. */
|
||||
function isPreModelSwitchHookBlock(error: unknown): boolean {
|
||||
return error instanceof Error && error.message.includes("blocked by a PreModelSwitch hook");
|
||||
}
|
||||
|
||||
async function readResumedLiveModel(
|
||||
query: Query,
|
||||
models: ModelInfo[],
|
||||
@@ -8430,11 +8578,30 @@ async function getAvailableModels(
|
||||
// containment in createSession). The SDK then stayed on the
|
||||
// transcript's model, so read that back rather than reporting the
|
||||
// pin the session isn't running.
|
||||
if (!isResumedSession) throw error;
|
||||
logger.error(`Failed to re-assert model "${currentModel.value}" on resume:`, error);
|
||||
const live = await readResumedLiveModel(query, models, logger);
|
||||
currentModel = live.model ?? currentModel;
|
||||
resumedContextWindow = live.contextWindow;
|
||||
//
|
||||
// One fresh-session failure is advisory, not defining: a
|
||||
// user-configured PreModelSwitch hook can veto the pin (CLI 2.1.251+;
|
||||
// 'deny', or 'ask' — which headless sessions refuse), and the SDK
|
||||
// rejects setModel with "Model switch blocked by a PreModelSwitch
|
||||
// hook: …". Terminal Claude Code never lets a hook veto its startup
|
||||
// model (the spawn model isn't a switch), so failing session/new here
|
||||
// would make the same hook config fatal only over ACP. The session
|
||||
// stays on the SDK's own default — report that. We can't read the
|
||||
// live model back on this path: getContextUsage isn't serviced on a
|
||||
// fresh session until the first prompt turn (issues #886/#880).
|
||||
if (!isResumedSession) {
|
||||
if (!isPreModelSwitchHookBlock(error)) throw error;
|
||||
logger.error(
|
||||
`Model pin "${currentModel.value}" was vetoed by a PreModelSwitch hook; staying on the default model:`,
|
||||
error,
|
||||
);
|
||||
currentModel = models[0];
|
||||
} else {
|
||||
logger.error(`Failed to re-assert model "${currentModel.value}" on resume:`, error);
|
||||
const live = await readResumedLiveModel(query, models, logger);
|
||||
currentModel = live.model ?? currentModel;
|
||||
resumedContextWindow = live.contextWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -387,9 +387,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
return { outcome: { outcome: "selected", optionId } };
|
||||
}
|
||||
|
||||
async unstable_createElicitation(
|
||||
params: CreateElicitationRequest,
|
||||
): Promise<CreateElicitationResponse> {
|
||||
async createElicitation(params: CreateElicitationRequest): Promise<CreateElicitationResponse> {
|
||||
this.elicitations.push(params);
|
||||
if (!CreateElicitationRequest.isForm(params)) {
|
||||
return { action: "decline" };
|
||||
@@ -466,9 +464,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
)
|
||||
.onRequest(methods.client.fs.readTextFile, (c) => client.readTextFile(c.params))
|
||||
.onRequest(methods.client.fs.writeTextFile, (c) => client.writeTextFile(c.params))
|
||||
.onRequest(methods.client.elicitation.create, (c) =>
|
||||
client.unstable_createElicitation(c.params),
|
||||
)
|
||||
.onRequest(methods.client.elicitation.create, (c) => client.createElicitation(c.params))
|
||||
.connect(stream);
|
||||
|
||||
await ctx.request(methods.agent.initialize, {
|
||||
@@ -1274,6 +1270,74 @@ describe("tool conversions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders a compact placeholder for a document block instead of its base64 payload", () => {
|
||||
// SDK 0.3.243 moved a PDF Read's document block inside the tool_result
|
||||
// content; dumping it through the default JSON.stringify branch would
|
||||
// send the entire base64 payload as a text block.
|
||||
const toolUse = {
|
||||
type: "tool_use",
|
||||
id: "toolu_01PDF",
|
||||
name: "Read",
|
||||
input: { file_path: "/Users/test/report.pdf" },
|
||||
};
|
||||
|
||||
const toolResult = {
|
||||
content: [
|
||||
{
|
||||
type: "document" as const,
|
||||
title: "Quarterly report",
|
||||
source: {
|
||||
type: "base64" as const,
|
||||
media_type: "application/pdf" as const,
|
||||
data: "A".repeat(4096),
|
||||
},
|
||||
},
|
||||
],
|
||||
tool_use_id: "toolu_01PDF",
|
||||
is_error: false,
|
||||
type: "tool_result" as const,
|
||||
};
|
||||
|
||||
const update = toolUpdateFromToolResult(toolResult as any, toolUse);
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: '[document "Quarterly report": application/pdf, 3.0 KB]',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("renders a url-sourced document block as its URL", () => {
|
||||
const toolResult = {
|
||||
content: [
|
||||
{
|
||||
type: "document" as const,
|
||||
source: { type: "url" as const, url: "https://example.com/spec.pdf" },
|
||||
},
|
||||
],
|
||||
tool_use_id: "toolu_01PDF",
|
||||
is_error: false,
|
||||
type: "tool_result" as const,
|
||||
};
|
||||
|
||||
const update = toolUpdateFromToolResult(toolResult as any, undefined);
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "[document: https://example.com/spec.pdf]" },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should transform tool_reference content to valid ACP content", () => {
|
||||
const toolUse = {
|
||||
type: "tool_use",
|
||||
@@ -1603,6 +1667,78 @@ describe("toolUpdateFromDiffToolResponse", () => {
|
||||
|
||||
expect(toolUpdateFromDiffToolResponse(toolResponse)).toEqual({});
|
||||
});
|
||||
|
||||
it("emits a whole-file diff for a Write update whose structuredPatch is empty", () => {
|
||||
// The empty-patch lanes (nothing changed / diff timed out) would
|
||||
// otherwise leave Write's optimistic oldText:null "creation" content
|
||||
// standing for an overwrite of an existing file.
|
||||
expect(
|
||||
toolUpdateFromDiffToolResponse({
|
||||
filePath: "/Users/test/project/file.ts",
|
||||
structuredPatch: [],
|
||||
type: "update",
|
||||
content: "new content",
|
||||
originalFile: "old content",
|
||||
}),
|
||||
).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "diff",
|
||||
path: "/Users/test/project/file.ts",
|
||||
oldText: "old content",
|
||||
newText: "new content",
|
||||
},
|
||||
],
|
||||
locations: [{ path: "/Users/test/project/file.ts" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("emits a truthful note for a Write update whose previous content was too large to diff", () => {
|
||||
// SDK 0.3.252 documents this lane: type "update" with an empty
|
||||
// structuredPatch AND originalFile null.
|
||||
expect(
|
||||
toolUpdateFromDiffToolResponse({
|
||||
filePath: "/Users/test/project/big.json",
|
||||
structuredPatch: [],
|
||||
type: "update",
|
||||
content: "new content",
|
||||
originalFile: null,
|
||||
}),
|
||||
).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "Updated `/Users/test/project/big.json` (previous content too large to diff)",
|
||||
},
|
||||
},
|
||||
],
|
||||
locations: [{ path: "/Users/test/project/big.json" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the empty return for a Write create and for Edit-shaped responses", () => {
|
||||
// A create's optimistic content already shows creation semantics.
|
||||
expect(
|
||||
toolUpdateFromDiffToolResponse({
|
||||
filePath: "/Users/test/project/new.ts",
|
||||
structuredPatch: [],
|
||||
type: "create",
|
||||
content: "new content",
|
||||
originalFile: null,
|
||||
}),
|
||||
).toEqual({});
|
||||
// FileEditOutput carries no `type`; its optimistic old/new diff is
|
||||
// truthful, so an empty patch still means "nothing to correct".
|
||||
expect(
|
||||
toolUpdateFromDiffToolResponse({
|
||||
filePath: "/Users/test/project/file.ts",
|
||||
structuredPatch: [],
|
||||
originalFile: "old content",
|
||||
}),
|
||||
).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("stripLocalCommandMetadata", () => {
|
||||
@@ -3611,7 +3747,7 @@ describe("subagent permission attribution (issue #851)", () => {
|
||||
requests.push(params);
|
||||
return { outcome: { outcome: "selected", optionId: "allow-once" } };
|
||||
},
|
||||
unstable_createElicitation: async (request: CreateElicitationRequest) => {
|
||||
createElicitation: async (request: CreateElicitationRequest) => {
|
||||
elicitations.push(request);
|
||||
return {
|
||||
action: "accept",
|
||||
@@ -11840,6 +11976,95 @@ describe("post-error recovery", () => {
|
||||
await agent.sessions["test-session"]?.consumer;
|
||||
});
|
||||
|
||||
it("uses a result's user_message_uuid to consume exactly the orphan it names", async () => {
|
||||
// SDK 0.3.246+ stamps results with the triggering send's client uuid.
|
||||
// With TWO cancelled commands pending (their dispatch frames lost), a
|
||||
// stamped orphan result must consume the entry it NAMES — not the oldest
|
||||
// — and the later /compact result, stamped with its own send, must not
|
||||
// be eaten by the dup-over-loss one-skip that the leftover pending entry
|
||||
// would otherwise trigger (which would hang the /compact prompt).
|
||||
const agent = createMockAgent();
|
||||
let release!: () => void;
|
||||
const gate = new Promise<void>((resolve) => (release = resolve));
|
||||
|
||||
injectGeneratorSession(agent, (input) => {
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const u1 = await iter.next();
|
||||
yield lifecycleInit;
|
||||
yield userEcho(u1.value); // turn 1 active
|
||||
await iter.next(); // turn 2 queued (cancelled below; every frame lost)
|
||||
const u3 = await iter.next(); // turn 3 queued (cancelled below)
|
||||
await gate;
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" }; // turn 1 settles cancelled
|
||||
// Both dispatch frames are LOST; only turn 3's dead turn flushes a
|
||||
// result, stamped with the send it answers.
|
||||
yield {
|
||||
...createResultMessage({
|
||||
subtype: "error_during_execution",
|
||||
stop_reason: null,
|
||||
is_error: true,
|
||||
}),
|
||||
user_message_uuid: u3.value.uuid,
|
||||
};
|
||||
const u4 = await iter.next(); // /compact's pushed message
|
||||
yield {
|
||||
type: "system",
|
||||
subtype: "status",
|
||||
status: "compacting",
|
||||
session_id: "test-session",
|
||||
};
|
||||
yield {
|
||||
...createResultMessage({ subtype: "success", stop_reason: "end_turn", is_error: false }),
|
||||
user_message_uuid: u4.value.uuid, // a live result names its own send
|
||||
};
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" };
|
||||
}
|
||||
return messageGenerator();
|
||||
});
|
||||
|
||||
const first = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "first" }],
|
||||
});
|
||||
await waitFor(() => !!agent.sessions["test-session"]?.activeTurn);
|
||||
await waitFor(() => !!agent.sessions["test-session"]?.msgLifecycleV1);
|
||||
const second = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "second" }],
|
||||
});
|
||||
const third = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "third" }],
|
||||
});
|
||||
await waitFor(() => (agent.sessions["test-session"]?.turnQueue?.length ?? 0) >= 3);
|
||||
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
expect(agent.sessions["test-session"]?.orphanCommands?.size).toBe(2);
|
||||
const compact = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "/compact" }],
|
||||
});
|
||||
release();
|
||||
|
||||
await expect(first).resolves.toEqual({
|
||||
stopReason: "cancelled",
|
||||
usage: cancelledTurnUsage,
|
||||
_meta: cancelledTurnQuotaMeta,
|
||||
});
|
||||
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
|
||||
await expect(third).resolves.toEqual({ stopReason: "cancelled" });
|
||||
// Settles on its OWN stamped result — under positional attribution the
|
||||
// one-skip would have consumed it for turn 2 and hung this prompt.
|
||||
const compactResult = await compact;
|
||||
expect(compactResult.stopReason).toBe("end_turn");
|
||||
expect(compactResult.usage?.inputTokens).toBe(10);
|
||||
// Turn 3's entry was consumed by name; turn 2's leftover pending entry
|
||||
// is swept by /compact's activation.
|
||||
expect(agent.sessions["test-session"]?.orphanCommands?.size).toBe(0);
|
||||
await agent.sessions["test-session"]?.consumer;
|
||||
});
|
||||
|
||||
it("deletes (not zombifies) an orphan whose `cancelled` frame arrives AFTER its error result", async () => {
|
||||
// The live-observed abort ordering is started → error result → cancelled.
|
||||
// The result deletes the "started" entry when it is skipped; the late
|
||||
@@ -14253,7 +14478,7 @@ describe("turn steering (_session/steering)", () => {
|
||||
{
|
||||
sessionUpdate: async () => {},
|
||||
requestPermission: userInputRequest,
|
||||
unstable_createElicitation: userInputRequest,
|
||||
createElicitation: userInputRequest,
|
||||
} as unknown as AcpClient,
|
||||
{ log: () => {}, error: () => {} },
|
||||
);
|
||||
@@ -14867,6 +15092,119 @@ describe("turn steering (_session/steering)", () => {
|
||||
expect(JSON.stringify(updates)).not.toContain("[Request interrupted by user]");
|
||||
});
|
||||
|
||||
it("swallows a stamped interruption diagnostic that names the cancelled command", async () => {
|
||||
// Same replay ordering as above, but the diagnostic carries the SDK
|
||||
// 0.3.246+ user_message_uuid stamp naming the cancelled first prompt —
|
||||
// the exact-join path must consume precisely that hand-off token.
|
||||
const agent = createMockAgent();
|
||||
let release!: () => void;
|
||||
const gate = new Promise<void>((resolve) => (release = resolve));
|
||||
|
||||
injectGeneratorSession(agent, (input) => {
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const original = await iter.next();
|
||||
await gate;
|
||||
const replacement = await iter.next();
|
||||
yield userEcho(original.value);
|
||||
yield userEcho({
|
||||
uuid: randomUUID(),
|
||||
message: { role: "user", content: "[Request interrupted by user]" },
|
||||
});
|
||||
yield userEcho(replacement.value);
|
||||
yield {
|
||||
...createResultMessage(),
|
||||
subtype: "success",
|
||||
is_error: true,
|
||||
result: "[ede_diagnostic] result_type=user last_content_type=n/a stop_reason=null",
|
||||
user_message_uuid: original.value.uuid,
|
||||
};
|
||||
yield createAssistantText("replacement answer");
|
||||
yield createResultMessage();
|
||||
yield idleMessage();
|
||||
}
|
||||
return messageGenerator();
|
||||
});
|
||||
|
||||
const first = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "first" }],
|
||||
});
|
||||
await waitFor(
|
||||
() =>
|
||||
agent.sessions["test-session"]?.turnQueue?.length === 1 &&
|
||||
agent.sessions["test-session"]?.activeTurn == null,
|
||||
);
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
|
||||
|
||||
const second = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "second" }],
|
||||
});
|
||||
release();
|
||||
|
||||
await expect(second).resolves.toEqual(expect.objectContaining({ stopReason: "end_turn" }));
|
||||
expect(
|
||||
agent.sessions["test-session"]?.pendingEmptyInterruptionDiagnosticCommands?.size ?? 0,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it("does not let a stale hand-off token swallow a live turn's stamped interruption diagnostic", async () => {
|
||||
// The diagnostic-shaped error is stamped with the LIVE replacement
|
||||
// prompt's uuid — not the cancelled command the token was handed out for.
|
||||
// The stamp positively refutes "this is the cancelled cycle's
|
||||
// diagnostic", so it must reach the ordinary failure lanes and fail the
|
||||
// replacement prompt instead of being silently eaten.
|
||||
const agent = createMockAgent();
|
||||
let release!: () => void;
|
||||
const gate = new Promise<void>((resolve) => (release = resolve));
|
||||
|
||||
injectGeneratorSession(agent, (input) => {
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const original = await iter.next();
|
||||
await gate;
|
||||
const replacement = await iter.next();
|
||||
yield userEcho(original.value);
|
||||
yield userEcho({
|
||||
uuid: randomUUID(),
|
||||
message: { role: "user", content: "[Request interrupted by user]" },
|
||||
});
|
||||
yield userEcho(replacement.value);
|
||||
yield {
|
||||
...createResultMessage(),
|
||||
subtype: "success",
|
||||
is_error: true,
|
||||
result: "[ede_diagnostic] result_type=user last_content_type=n/a stop_reason=null",
|
||||
user_message_uuid: replacement.value.uuid,
|
||||
};
|
||||
yield idleMessage();
|
||||
}
|
||||
return messageGenerator();
|
||||
});
|
||||
|
||||
const first = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "first" }],
|
||||
});
|
||||
await waitFor(
|
||||
() =>
|
||||
agent.sessions["test-session"]?.turnQueue?.length === 1 &&
|
||||
agent.sessions["test-session"]?.activeTurn == null,
|
||||
);
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
|
||||
|
||||
const second = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "second" }],
|
||||
});
|
||||
release();
|
||||
|
||||
await expect(second).rejects.toThrow("[ede_diagnostic]");
|
||||
});
|
||||
|
||||
it("does not hide an empty user-interruption diagnostic without a cancelled predecessor", async () => {
|
||||
const agent = createMockAgent();
|
||||
injectGeneratorSession(agent, (input) => {
|
||||
|
||||
@@ -13,6 +13,8 @@ import * as path from "node:path";
|
||||
|
||||
let capturedOptions: Options | undefined;
|
||||
let contextUsageResult: (() => Promise<{ rawMaxTokens: number; model?: string }>) | undefined;
|
||||
let initModels: Record<string, unknown>[] | undefined;
|
||||
let setModelImpl: ((model: string) => Promise<void>) | undefined;
|
||||
let mcpServerStatusResult: () => Promise<Array<{ name: string; status: string }>>;
|
||||
let mcpAuthenticateImpl: (serverName: string) => Promise<{
|
||||
authUrl?: string;
|
||||
@@ -30,7 +32,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
capturedOptions = args.options;
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
models: initModels ?? [
|
||||
{
|
||||
value: "claude-sonnet-4-6",
|
||||
displayName: "Claude Sonnet",
|
||||
@@ -39,6 +41,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
},
|
||||
],
|
||||
}),
|
||||
setModel: (model: string) => (setModelImpl ? setModelImpl(model) : Promise.resolve()),
|
||||
getContextUsage: () =>
|
||||
contextUsageResult ? contextUsageResult() : Promise.resolve(DEFAULT_CONTEXT_USAGE),
|
||||
mcpServerStatus: () => mcpServerStatusResult(),
|
||||
@@ -72,6 +75,8 @@ describe("createSession options merging", () => {
|
||||
beforeEach(async () => {
|
||||
capturedOptions = undefined;
|
||||
contextUsageResult = undefined;
|
||||
initModels = undefined;
|
||||
setModelImpl = undefined;
|
||||
mcpServerStatusResult = async () => [];
|
||||
mcpAuthenticateImpl = async () => ({
|
||||
requiresUserAction: false,
|
||||
@@ -685,8 +690,8 @@ describe("createSession options merging", () => {
|
||||
}));
|
||||
const completeElicitation = vi.fn(async () => {});
|
||||
Object.assign((agent as unknown as { client: object }).client, {
|
||||
unstable_createElicitation: createElicitation,
|
||||
unstable_completeElicitation: completeElicitation,
|
||||
createElicitation,
|
||||
completeElicitation,
|
||||
});
|
||||
|
||||
await agent.initialize({
|
||||
@@ -871,9 +876,8 @@ describe("createSession options merging", () => {
|
||||
});
|
||||
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
const createElicitation = vi.fn();
|
||||
(
|
||||
agent as unknown as { client: { unstable_createElicitation: unknown } }
|
||||
).client.unstable_createElicitation = createElicitation;
|
||||
(agent as unknown as { client: { createElicitation: unknown } }).client.createElicitation =
|
||||
createElicitation;
|
||||
return { onUserDialog: capturedOptions!.onUserDialog!, createElicitation };
|
||||
}
|
||||
|
||||
@@ -969,4 +973,140 @@ describe("createSession options merging", () => {
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("client exploded"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("model switches and PreModelSwitch hooks", () => {
|
||||
const TWO_MODELS = [
|
||||
{
|
||||
value: "claude-sonnet-4-6",
|
||||
displayName: "Claude Sonnet",
|
||||
description: "Fast",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
{
|
||||
value: "claude-opus-4-8",
|
||||
displayName: "Claude Opus",
|
||||
description: "Capable",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
];
|
||||
|
||||
let originalAnthropicModel: string | undefined;
|
||||
beforeEach(() => {
|
||||
originalAnthropicModel = process.env.ANTHROPIC_MODEL;
|
||||
delete process.env.ANTHROPIC_MODEL;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (originalAnthropicModel !== undefined) {
|
||||
process.env.ANTHROPIC_MODEL = originalAnthropicModel;
|
||||
} else {
|
||||
delete process.env.ANTHROPIC_MODEL;
|
||||
}
|
||||
});
|
||||
|
||||
it("tolerates a PreModelSwitch hook vetoing the fresh-session model pin", async () => {
|
||||
// CLI 2.1.251+: a user-configured PreModelSwitch hook can deny (or
|
||||
// 'ask', which headless sessions refuse) the pin's setModel. Terminal
|
||||
// Claude Code never lets a hook veto its startup model, so the same
|
||||
// config must not fail session/new over ACP — the session stays on the
|
||||
// SDK's default, and we report that.
|
||||
initModels = TWO_MODELS;
|
||||
process.env.ANTHROPIC_MODEL = "opus";
|
||||
setModelImpl = () =>
|
||||
Promise.reject(new Error("Model switch blocked by a PreModelSwitch hook: pinned by IT"));
|
||||
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
|
||||
expect((agent as any).sessions[response.sessionId].models.currentModelId).toBe(
|
||||
"claude-sonnet-4-6",
|
||||
);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("vetoed by a PreModelSwitch hook"),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("still fails session/new loudly on a non-hook setModel failure", async () => {
|
||||
initModels = TWO_MODELS;
|
||||
process.env.ANTHROPIC_MODEL = "opus";
|
||||
setModelImpl = () => Promise.reject(new Error("transport exploded"));
|
||||
|
||||
await expect(agent.newSession({ cwd: process.cwd(), mcpServers: [] })).rejects.toThrow(
|
||||
"transport exploded",
|
||||
);
|
||||
});
|
||||
|
||||
it("syncs adapter model state when a PostModelSwitch hook reports an external switch", async () => {
|
||||
// A `/model <name>` command typed as a prompt switches the session's
|
||||
// model with no refusal-fallback frame; the registered PostModelSwitch
|
||||
// hook is what keeps the ACP picker truthful.
|
||||
initModels = TWO_MODELS;
|
||||
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
const sessionId = response.sessionId;
|
||||
|
||||
const matchers = capturedOptions!.hooks?.PostModelSwitch;
|
||||
expect(matchers).toBeDefined();
|
||||
const callback = (matchers!.at(-1) as any).hooks[0];
|
||||
|
||||
const hookInput = {
|
||||
hook_event_name: "PostModelSwitch",
|
||||
session_id: sessionId,
|
||||
transcript_path: "",
|
||||
cwd: process.cwd(),
|
||||
from_model: "claude-sonnet-4-6",
|
||||
to_model: "claude-opus-4-8",
|
||||
requested_model: "opus",
|
||||
source: "command",
|
||||
context_tokens: 0,
|
||||
prompt_cache_warm: false,
|
||||
cache_ttl: "5m",
|
||||
estimated_cache_write_usd: 0,
|
||||
pricing: "catalog",
|
||||
};
|
||||
const out = await callback(hookInput, undefined, {
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
expect(out).toEqual({ continue: true });
|
||||
|
||||
// The sync is detached from the hook response (control requests are
|
||||
// serialized); wait for it to land.
|
||||
await vi.waitFor(() => {
|
||||
expect((agent as any).sessions[sessionId].models.currentModelId).toBe("claude-opus-4-8");
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores PostModelSwitch reports for the adapter's own setModel calls", async () => {
|
||||
initModels = TWO_MODELS;
|
||||
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
const sessionId = response.sessionId;
|
||||
const callback = (capturedOptions!.hooks!.PostModelSwitch!.at(-1) as any).hooks[0];
|
||||
|
||||
await callback(
|
||||
{
|
||||
hook_event_name: "PostModelSwitch",
|
||||
session_id: sessionId,
|
||||
transcript_path: "",
|
||||
cwd: process.cwd(),
|
||||
from_model: "claude-sonnet-4-6",
|
||||
to_model: "claude-opus-4-8",
|
||||
requested_model: "opus",
|
||||
source: "sdk",
|
||||
context_tokens: 0,
|
||||
prompt_cache_warm: false,
|
||||
cache_ttl: "5m",
|
||||
estimated_cache_write_usd: 0,
|
||||
pricing: "catalog",
|
||||
},
|
||||
undefined,
|
||||
{ signal: new AbortController().signal },
|
||||
);
|
||||
|
||||
// Detached-sync window: give a stray sync the chance to land, then
|
||||
// assert nothing moved.
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect((agent as any).sessions[sessionId].models.currentModelId).toBe("claude-sonnet-4-6");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -126,8 +126,8 @@ async function createHarness(): Promise<LiveHarness> {
|
||||
},
|
||||
readTextFile: async () => ({ content: "" }),
|
||||
writeTextFile: async () => ({}),
|
||||
unstable_createElicitation: async () => ({ action: "decline" as const }),
|
||||
unstable_completeElicitation: async () => {},
|
||||
createElicitation: async () => ({ action: "decline" as const }),
|
||||
completeElicitation: async () => {},
|
||||
extNotification: async (method: string, params: JsonObject) => {
|
||||
if (method !== "_claude/sdkMessage") return;
|
||||
const message = object(params.message);
|
||||
|
||||
@@ -130,8 +130,8 @@ async function createHarness(): Promise<LiveHarness> {
|
||||
},
|
||||
readTextFile: async () => ({ content: "" }),
|
||||
writeTextFile: async () => ({}),
|
||||
unstable_createElicitation: async () => ({ action: "decline" as const }),
|
||||
unstable_completeElicitation: async () => {},
|
||||
createElicitation: async () => ({ action: "decline" as const }),
|
||||
completeElicitation: async () => {},
|
||||
extNotification: async (method: string, params: JsonObject) => {
|
||||
if (method !== "_claude/sdkMessage") return;
|
||||
const message = object(params.message);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
resolveModelPreference,
|
||||
applyAvailableModelsAllowlist,
|
||||
matchResumedModel,
|
||||
settingsEffortForModel,
|
||||
} from "../acp-agent.js";
|
||||
|
||||
// Mirrors a real `supportedModels()` response: alias rows carry
|
||||
@@ -270,3 +271,43 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("model resolution (real SDK)
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
describe("settingsEffortForModel", () => {
|
||||
const SONNET: ModelInfo = {
|
||||
value: "sonnet",
|
||||
resolvedModel: "claude-sonnet-5",
|
||||
displayName: "Sonnet",
|
||||
description: "Sonnet 5 · Efficient for routine tasks",
|
||||
};
|
||||
|
||||
it("prefers the per-model entry keyed by the resolved model id", () => {
|
||||
const settings = {
|
||||
effortLevel: "high" as const,
|
||||
modelSettings: { "claude-sonnet-5": { effortLevel: "low" as const } },
|
||||
};
|
||||
expect(settingsEffortForModel(settings, SONNET)).toBe("low");
|
||||
});
|
||||
|
||||
it("falls back to the picker row value, then the raw model id", () => {
|
||||
const byValue = { modelSettings: { sonnet: { effortLevel: "medium" as const } } };
|
||||
expect(settingsEffortForModel(byValue, SONNET)).toBe("medium");
|
||||
|
||||
const byRawId = { modelSettings: { "claude-x": { effortLevel: "xhigh" as const } } };
|
||||
expect(settingsEffortForModel(byRawId, undefined, "claude-x")).toBe("xhigh");
|
||||
});
|
||||
|
||||
it("falls back to the top-level effortLevel when no per-model entry matches", () => {
|
||||
expect(settingsEffortForModel({ effortLevel: "high" }, SONNET)).toBe("high");
|
||||
expect(
|
||||
settingsEffortForModel(
|
||||
{ effortLevel: "high", modelSettings: { "claude-opus-4-8": { effortLevel: "low" } } },
|
||||
SONNET,
|
||||
),
|
||||
).toBe("high");
|
||||
});
|
||||
|
||||
it("returns undefined when neither layer sets an effort", () => {
|
||||
expect(settingsEffortForModel({}, SONNET)).toBeUndefined();
|
||||
expect(settingsEffortForModel({ modelSettings: {} }, undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ describe("session config options", () => {
|
||||
input: null,
|
||||
cancelled: false,
|
||||
permissionMode: "default",
|
||||
settingsManager: {},
|
||||
settingsManager: { getSettings: () => ({}) },
|
||||
modes: structuredClone(MOCK_MODES),
|
||||
models: structuredClone(MOCK_MODELS),
|
||||
modelInfos: MOCK_MODELS.availableModels.map((m): ModelInfo => ({
|
||||
@@ -419,14 +419,18 @@ describe("session config options", () => {
|
||||
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption).toBeUndefined();
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
// Nothing was pinned at the flag layer (effort was "default"), so there
|
||||
// is nothing to clear — the CLI resolves its own effort for the new model.
|
||||
expect(applyFlagSettingsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clamps effort in returned configOptions when new model has different supported levels", async () => {
|
||||
// Set current effort to "max" which the new model won't support
|
||||
// Set current effort to "max" which the new model won't support —
|
||||
// pinned, as a user's ACP picker choice would be.
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
const effortOpt = session.configOptions.find((o: any) => o.id === "effort");
|
||||
if (effortOpt) effortOpt.currentValue = "max";
|
||||
session.effortPinnedByUser = true;
|
||||
|
||||
session.modelInfos = [
|
||||
{
|
||||
@@ -458,10 +462,12 @@ describe("session config options", () => {
|
||||
});
|
||||
|
||||
it("preserves effort in returned configOptions when new model supports same level", async () => {
|
||||
// Set effort to "low" first
|
||||
// Set effort to "low" first — pinned, as a user's ACP picker choice
|
||||
// would be (an unpinned value re-seeds from settings on a switch).
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
const effortOpt = session.configOptions.find((o: any) => o.id === "effort");
|
||||
if (effortOpt) effortOpt.currentValue = "low";
|
||||
session.effortPinnedByUser = true;
|
||||
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
@@ -630,7 +636,7 @@ describe("session config options", () => {
|
||||
expect(effortOption).toBeUndefined();
|
||||
});
|
||||
|
||||
it("clears effort via applyFlagSettings when switching to a model without effort", async () => {
|
||||
it("clears a pinned effort via applyFlagSettings when switching to a model without effort", async () => {
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
session.modelInfos = [
|
||||
{
|
||||
@@ -648,6 +654,14 @@ describe("session config options", () => {
|
||||
},
|
||||
];
|
||||
|
||||
// Pin an effort the way a user would, then switch to a model that
|
||||
// cannot serve it: the flag layer must be cleared alongside, or the
|
||||
// SDK would keep running the old pin invisibly.
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "effort",
|
||||
value: "high",
|
||||
});
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
@@ -655,6 +669,8 @@ describe("session config options", () => {
|
||||
});
|
||||
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
// The clamp un-pins: a later switch back re-seeds from settings.
|
||||
expect(session.effortPinnedByUser).toBe(false);
|
||||
});
|
||||
|
||||
it("adds effort option when switching to a model that supports effort", async () => {
|
||||
@@ -693,9 +709,11 @@ describe("session config options", () => {
|
||||
|
||||
it("clamps effort to valid value when new model has different supported levels", async () => {
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
// Set current effort to "max" (not supported by sonnet in our mock)
|
||||
// Set current effort to "max" (not supported by sonnet in our mock) —
|
||||
// pinned, as a user's ACP picker choice would be.
|
||||
const effortOpt = session.configOptions.find((o: any) => o.id === "effort");
|
||||
if (effortOpt) effortOpt.currentValue = "max";
|
||||
session.effortPinnedByUser = true;
|
||||
|
||||
session.modelInfos = [
|
||||
{
|
||||
@@ -748,6 +766,48 @@ describe("session config options", () => {
|
||||
// applyFlagSettings was called once for the effort change, but not again for the model switch
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("seeds effort from the new model's persisted modelSettings entry on an unpinned switch", async () => {
|
||||
// The CLI persists /effort per model (settings.modelSettings); with no
|
||||
// user pin this session, the picker should show what the CLI will
|
||||
// actually run on the new model, not drag the old model's value along.
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
session.settingsManager = {
|
||||
getSettings: () => ({
|
||||
effortLevel: "high",
|
||||
modelSettings: { "claude-sonnet-4-6": { effortLevel: "low" } },
|
||||
}),
|
||||
};
|
||||
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption?.currentValue).toBe("low");
|
||||
// Display-only: the CLI resolves persisted effort itself; pinning it at
|
||||
// the flag layer would shadow the per-model values on later switches.
|
||||
expect(applyFlagSettingsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the top-level settings effort when the new model has no per-model entry", async () => {
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
session.settingsManager = {
|
||||
getSettings: () => ({ effortLevel: "medium" }),
|
||||
};
|
||||
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption?.currentValue).toBe("medium");
|
||||
expect(applyFlagSettingsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("bidirectional consistency", () => {
|
||||
|
||||
@@ -64,7 +64,7 @@ export function mockSessionState(
|
||||
modes: { currentModeId: "default", availableModes: [] },
|
||||
models: { currentModelId: "default", availableModels: [] },
|
||||
modelInfos: [],
|
||||
settingsManager: { dispose: vi.fn() },
|
||||
settingsManager: { dispose: vi.fn(), getSettings: () => ({}) },
|
||||
accumulatedUsage: {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
|
||||
@@ -2596,6 +2596,71 @@ describe("Agent/Task tool_result rendering from tool_use_result", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const PARTIAL_NOTE =
|
||||
"NOTE: this agent stopped at its 30-turn limit before finishing. The text below is PARTIAL output; treat it as incomplete. Send the agent a message (SendMessage) to let it continue from where it stopped.";
|
||||
const PARTIAL_LABEL = "[Agent stopped at its turn limit — the output below is partial]";
|
||||
|
||||
it("replaces the maxTurns partial-output note in the structured lane", () => {
|
||||
// A maxTurns-stopped subagent still ships status "completed", with a
|
||||
// model-directed note block leading the report — the SendMessage
|
||||
// instruction is meaningless over ACP, but the partial-output fact isn't.
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, {
|
||||
...structured,
|
||||
content: [
|
||||
{ type: "text", text: PARTIAL_NOTE },
|
||||
{ type: "text", text: "The partial report." },
|
||||
],
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: PARTIAL_LABEL } },
|
||||
{ type: "content", content: { type: "text", text: "The partial report." } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("replaces the note variant for an agent that produced no report", () => {
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, {
|
||||
...structured,
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "NOTE: this agent stopped at its 5-turn limit before finishing. It was still calling tools and had produced no report.",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: PARTIAL_LABEL } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("replaces the note paragraph in the raw fallback and still strips the trailer", () => {
|
||||
const result: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_agent",
|
||||
content: [{ type: "text", text: `${PARTIAL_NOTE}\n\nThe report.${TRAILER}` }],
|
||||
};
|
||||
const update = toolUpdateFromToolResult(result, agentToolUse, false);
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: `${PARTIAL_LABEL}\n\nThe report.` } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("leaves a report that merely mentions the note text mid-block alone", () => {
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, {
|
||||
...structured,
|
||||
content: [{ type: "text", text: `Quoting the harness: "${PARTIAL_NOTE}"` }],
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: `Quoting the harness: "${PARTIAL_NOTE}"` },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
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.
|
||||
|
||||
127
src/tools.ts
127
src/tools.ts
@@ -30,6 +30,7 @@ import {
|
||||
WebSearchOutput,
|
||||
} from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
|
||||
import {
|
||||
DocumentBlockParam,
|
||||
ImageBlockParam,
|
||||
TextBlockParam,
|
||||
ToolResultBlockParam,
|
||||
@@ -69,6 +70,7 @@ import path from "node:path";
|
||||
*/
|
||||
type ToolResultContent =
|
||||
| TextBlockParam
|
||||
| DocumentBlockParam
|
||||
| ImageBlockParam
|
||||
| BetaImageBlockParam
|
||||
| BetaToolReferenceBlock
|
||||
@@ -574,6 +576,50 @@ function stripAgentTrailerFromContent(content: unknown): unknown {
|
||||
return content;
|
||||
}
|
||||
|
||||
/** Leading model-directed note the CLI prepends to a subagent's report when
|
||||
* the agent stopped at its maxTurns limit (CLI 2.1.246+); the result still
|
||||
* ships as `status: "completed"`. Two body variants follow this prefix, and
|
||||
* the trailing "Send the agent a message (SendMessage) …" sentence is
|
||||
* omitted for some agent types — anchor only the stable prefix so a format
|
||||
* change makes the replacement stop matching rather than mangle a report. */
|
||||
const PARTIAL_OUTPUT_NOTE = /^NOTE: this agent stopped at its \d+-turn limit before finishing\./;
|
||||
|
||||
/** Client-facing replacement: the partial-output fact matters to the user,
|
||||
* but the SendMessage continuation instruction is model-directed and
|
||||
* meaningless over ACP. */
|
||||
const PARTIAL_OUTPUT_LABEL = "[Agent stopped at its turn limit — the output below is partial]";
|
||||
|
||||
/** Replace a leading partial-output note paragraph with the concise
|
||||
* client-facing label, leaving the report that follows intact. */
|
||||
function replacePartialNoteInText(text: string): string {
|
||||
if (!PARTIAL_OUTPUT_NOTE.test(text)) return text;
|
||||
const paragraphEnd = text.indexOf("\n\n");
|
||||
const report = paragraphEnd === -1 ? "" : text.slice(paragraphEnd + 2).trimStart();
|
||||
return report ? `${PARTIAL_OUTPUT_LABEL}\n\n${report}` : PARTIAL_OUTPUT_LABEL;
|
||||
}
|
||||
|
||||
/** Apply {@link replacePartialNoteInText} to an Agent/Task result `content`.
|
||||
* In the structured AgentOutput lane the note is its own leading text block;
|
||||
* in the raw lane it is the first paragraph of the text — both reduce to
|
||||
* transforming the first text block (or the plain string). */
|
||||
function replacePartialOutputNote(content: unknown): unknown {
|
||||
if (typeof content === "string") {
|
||||
return replacePartialNoteInText(content);
|
||||
}
|
||||
if (Array.isArray(content) && content.length > 0) {
|
||||
const [first, ...rest] = content;
|
||||
if (
|
||||
first !== null &&
|
||||
typeof first === "object" &&
|
||||
first.type === "text" &&
|
||||
typeof first.text === "string"
|
||||
) {
|
||||
return [{ ...first, text: replacePartialNoteInText(first.text) }, ...rest];
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
export function toolUpdateFromToolResult(
|
||||
toolResult:
|
||||
| ToolResultBlockParam
|
||||
@@ -847,7 +893,10 @@ export function toolUpdateFromToolResult(
|
||||
structured.content.length > 0
|
||||
) {
|
||||
return toAcpContentUpdate(
|
||||
structured.content,
|
||||
// A maxTurns-stopped subagent still completes, with a model-directed
|
||||
// partial-output note prepended to its report — swap it for a
|
||||
// client-facing label (see PARTIAL_OUTPUT_NOTE).
|
||||
replacePartialOutputNote(structured.content),
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
}
|
||||
@@ -859,7 +908,9 @@ export function toolUpdateFromToolResult(
|
||||
// format changes it simply stops matching and the full raw text
|
||||
// renders, no worse than before.
|
||||
return toAcpContentUpdate(
|
||||
stripAgentTrailerFromContent(toolResult.content),
|
||||
// Head and tail cleanups are independent: the partial-output note
|
||||
// leads the raw text the same way it leads the structured content.
|
||||
replacePartialOutputNote(stripAgentTrailerFromContent(toolResult.content)),
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
}
|
||||
@@ -925,6 +976,14 @@ function formatWebSearchHit(hit: { title: string; url: string }): string {
|
||||
return `${hit.title} (${hit.url})`;
|
||||
}
|
||||
|
||||
/** Human-readable size for the document placeholder ("312 B", "2.4 KB",
|
||||
* "1.3 MB"). */
|
||||
function formatByteSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function toAcpContentUpdate(
|
||||
content: any,
|
||||
isError: boolean = false,
|
||||
@@ -988,6 +1047,33 @@ function toAcpContentBlock(content: ToolResultContent, isError: boolean): Conten
|
||||
: "[image: file reference]",
|
||||
);
|
||||
|
||||
case "document": {
|
||||
// A PDF Read delivers its raw `document` block inside the tool_result
|
||||
// content (SDK 0.3.243 moved it here from a separate follow-up user
|
||||
// message; the MultiRead documents lane always lived here). ACP has no
|
||||
// document block and the base64 payload can be megabytes — render a
|
||||
// compact placeholder, never the data, matching the CLI's own compact
|
||||
// "Read PDF (size)" rendering.
|
||||
const title =
|
||||
typeof content.title === "string" && content.title.length > 0 ? ` "${content.title}"` : "";
|
||||
const source = content.source;
|
||||
switch (source.type) {
|
||||
case "url":
|
||||
return wrapText(`[document${title}: ${source.url}]`);
|
||||
case "base64":
|
||||
case "text":
|
||||
// base64 inflates the byte count by 4/3; plain text is 1:1.
|
||||
return wrapText(
|
||||
`[document${title}: ${source.media_type}, ${formatByteSize(
|
||||
source.type === "base64"
|
||||
? Math.floor((source.data.length * 3) / 4)
|
||||
: source.data.length,
|
||||
)}]`,
|
||||
);
|
||||
default:
|
||||
return wrapText(`[document${title}]`);
|
||||
}
|
||||
}
|
||||
case "tool_reference":
|
||||
return wrapText(`Tool: ${content.tool_name}`);
|
||||
case "tool_search_tool_search_result":
|
||||
@@ -1286,6 +1372,14 @@ interface DiffToolResponseHunk {
|
||||
interface DiffToolResponse {
|
||||
filePath?: string;
|
||||
structuredPatch?: DiffToolResponseHunk[];
|
||||
/** FileWriteOutput only (FileEditOutput carries no `type`): whether the
|
||||
* write created the file or overwrote an existing one. */
|
||||
type?: "create" | "update";
|
||||
/** FileWriteOutput only: the content that was written. */
|
||||
content?: string;
|
||||
/** FileWriteOutput only: the pre-write content — null on create, or on an
|
||||
* update whose previous content was too large to include. */
|
||||
originalFile?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1330,6 +1424,35 @@ export function toolUpdateFromDiffToolResponse(toolResponse: unknown): {
|
||||
}
|
||||
}
|
||||
|
||||
// A Write `update` can arrive with an empty structuredPatch — nothing
|
||||
// changed, the diff timed out, or the previous content was too large to
|
||||
// diff (originalFile null; SDK 0.3.252 documents the lane). Returning `{}`
|
||||
// would leave Write's optimistic tool_use-time content standing, and that
|
||||
// was built with `oldText: null` — "creation" semantics — so an overwrite
|
||||
// of a large existing file would render as creating it. Emit a truthful
|
||||
// replacement instead. Gated on `type` so Edit (whose output carries no
|
||||
// `type` and whose optimistic old/new diff is already truthful) keeps the
|
||||
// empty-return behavior.
|
||||
if (content.length === 0 && response.type === "update" && typeof response.content === "string") {
|
||||
locations.push({ path: response.filePath });
|
||||
content.push(
|
||||
typeof response.originalFile === "string"
|
||||
? {
|
||||
type: "diff",
|
||||
path: response.filePath,
|
||||
oldText: response.originalFile,
|
||||
newText: response.content,
|
||||
}
|
||||
: {
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: `Updated \`${response.filePath}\` (previous content too large to diff)`,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const result: { content?: ToolCallContent[]; locations?: ToolCallLocation[] } = {};
|
||||
if (content.length > 0) result.content = content;
|
||||
if (locations.length > 0) result.locations = locations;
|
||||
|
||||
Reference in New Issue
Block a user