Compare commits

...

5 Commits

Author SHA1 Message Date
Ben Brandt
600a2db82f More efficient file reads (#73)
Allows the client to handle line offsets, rather than loading the entire
file and then slicing it on the agent side.

Signed-off-by: Ben Brandt <benjamin.j.brandt@gmail.com>

Signed-off-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-09-29 13:54:30 +02:00
Ben Brandt
f2a4b3d19b claude 1.0.128 (#72)
- **Update to 1.0.128 + use anthropic sdk types**
- **Fix compacting output**
- **Handle JSON errors**

---------

Signed-off-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-09-29 12:59:59 +02:00
Ben Brandt
9135a0f1be Update to claude-code-sdk v1.0.124 (#68) 2025-09-25 15:05:47 +02:00
Ben Brandt
92c5cd84e8 Update clients list (#67) 2025-09-25 12:26:40 +00:00
Christian Tietze
8311a7a222 add Emacs client to README (#66) 2025-09-25 12:21:43 +00:00
10 changed files with 286 additions and 310 deletions

View File

@@ -1,5 +1,18 @@
# Changelog
## v0.4.7
- More efficient file reads from the client.
## v0.4.6
- Update to @anthropic-ai/claude-code@v1.0.128
## v0.4.5
- Update to @anthropic-ai/claude-code@v1.0.124
- Update to @zed-industries/agent-client-protocol@v0.4.3
## v0.4.4
- Update to @anthropic-ai/claude-code@v1.0.123

View File

@@ -32,8 +32,11 @@ Read the docs on [External Agent](https://zed.dev/docs/ai/external-agents) suppo
### Other clients
- Neovim via [CodeCompanion.nvim](https://codecompanion.olimorris.dev/configuration/adapters#setup-claude-code-via-acp)
- Emacs via [agent-shell.el](https://github.com/xenodium/agent-shell)
- [marimo notebook](https://github.com/marimo-team/marimo)
- Neovim
- via [CodeCompanion.nvim](https://codecompanion.olimorris.dev/configuration/adapters#setup-claude-code-via-acp)
- via [yetone/avante.nvim](https://github.com/yetone/avante.nvim)
[Submit a PR](https://github.com/zed-industries/claude-code-acp/pulls) to add yours!

269
package-lock.json generated
View File

@@ -1,17 +1,17 @@
{
"name": "@zed-industries/claude-code-acp",
"version": "0.4.4",
"version": "0.4.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@zed-industries/claude-code-acp",
"version": "0.4.4",
"version": "0.4.7",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/claude-code": "1.0.123",
"@modelcontextprotocol/sdk": "1.18.1",
"@zed-industries/agent-client-protocol": "0.4.2",
"@anthropic-ai/claude-code": "1.0.128",
"@modelcontextprotocol/sdk": "1.18.2",
"@zed-industries/agent-client-protocol": "0.4.3",
"diff": "8.0.2",
"express": "5.1.0",
"minimist": "1.2.8",
@@ -21,6 +21,7 @@
"claude-code-acp": "dist/index.js"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.64.0",
"@types/express": "5.0.3",
"@types/minimist": "1.2.5",
"@types/node": "24.5.2",
@@ -38,9 +39,9 @@
}
},
"node_modules/@anthropic-ai/claude-code": {
"version": "1.0.123",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.123.tgz",
"integrity": "sha512-egniOpnrjpm8pA2ygj9aDw+rZ0jKJdso94CvKlKzdVcLdw0r0FSMvgxWRGGxjHuqcl6BP6yhHGSKlZ6qzd14OA==",
"version": "1.0.128",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.128.tgz",
"integrity": "sha512-uUg5cFMJfeQetQzFw76Vpbro6DAXst2Lpu8aoZWRFSoQVYu5ZSAnbBoxaWmW/IgnHSqIIvtMwzCoqmcA9j9rNQ==",
"license": "SEE LICENSE IN README.md",
"bin": {
"claude": "cli.js"
@@ -57,6 +58,37 @@
"@img/sharp-win32-x64": "^0.33.5"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.64.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.64.0.tgz",
"integrity": "sha512-K2JaWzPiIA4/ghD7bMlyM/4JvM3k7sKdHFmXWAKqqpVL9ee1H7pguRqygxTgK+SrzK86ZMf1x3L+RK4IrDtxjA==",
"dev": true,
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1"
},
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -1001,9 +1033,9 @@
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.1.tgz",
"integrity": "sha512-d//GE8/Yh7aC3e7p+kZG8JqqEAwwDUmAfvH1quogtbk+ksS6E0RR6toKKESPYYZVre0meqkJb27zb+dhqE9Sgw==",
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.2.tgz",
"integrity": "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg==",
"license": "MIT",
"dependencies": {
"ajv": "^6.12.6",
@@ -1069,9 +1101,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
"cpu": [
"arm"
],
@@ -1083,9 +1115,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
"cpu": [
"arm64"
],
@@ -1097,9 +1129,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
"cpu": [
"arm64"
],
@@ -1111,9 +1143,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
"cpu": [
"x64"
],
@@ -1125,9 +1157,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
"cpu": [
"arm64"
],
@@ -1139,9 +1171,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
"cpu": [
"x64"
],
@@ -1153,9 +1185,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
"cpu": [
"arm"
],
@@ -1167,9 +1199,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
"cpu": [
"arm"
],
@@ -1181,9 +1213,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
"cpu": [
"arm64"
],
@@ -1195,9 +1227,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
"cpu": [
"arm64"
],
@@ -1209,9 +1241,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
"cpu": [
"loong64"
],
@@ -1223,9 +1255,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
"cpu": [
"ppc64"
],
@@ -1237,9 +1269,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
"cpu": [
"riscv64"
],
@@ -1251,9 +1283,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
"cpu": [
"riscv64"
],
@@ -1265,9 +1297,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
"cpu": [
"s390x"
],
@@ -1279,9 +1311,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
"cpu": [
"x64"
],
@@ -1293,9 +1325,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
"cpu": [
"x64"
],
@@ -1307,9 +1339,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
"cpu": [
"arm64"
],
@@ -1321,9 +1353,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
"cpu": [
"arm64"
],
@@ -1335,9 +1367,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
"cpu": [
"ia32"
],
@@ -1349,9 +1381,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
"cpu": [
"x64"
],
@@ -1363,9 +1395,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
"cpu": [
"x64"
],
@@ -1929,9 +1961,9 @@
}
},
"node_modules/@zed-industries/agent-client-protocol": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@zed-industries/agent-client-protocol/-/agent-client-protocol-0.4.2.tgz",
"integrity": "sha512-i131nVbxqDBkKVxt7ThqU3wHk96CKcfiHOn9kfKIB1SkI1NkyeZvSR+cXFttCUxqTR/JShrDbDLWqmUHcmajsg==",
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@zed-industries/agent-client-protocol/-/agent-client-protocol-0.4.3.tgz",
"integrity": "sha512-6lcxHVdkm0YMvqBNJyddXA4T8qhVMeOVscXwioA8hRMLhroLigsspH8oQEbuL35r3g+b2LcmhVzu8EijUo4DYw==",
"license": "Apache-2.0",
"dependencies": {
"zod": "^3.0.0"
@@ -3277,6 +3309,20 @@
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-to-ts": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
"integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
"ts-algebra": "^2.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -3876,9 +3922,9 @@
}
},
"node_modules/rollup": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3892,28 +3938,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.2",
"@rollup/rollup-android-arm64": "4.52.2",
"@rollup/rollup-darwin-arm64": "4.52.2",
"@rollup/rollup-darwin-x64": "4.52.2",
"@rollup/rollup-freebsd-arm64": "4.52.2",
"@rollup/rollup-freebsd-x64": "4.52.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
"@rollup/rollup-linux-arm64-musl": "4.52.2",
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
"@rollup/rollup-linux-x64-gnu": "4.52.2",
"@rollup/rollup-linux-x64-musl": "4.52.2",
"@rollup/rollup-openharmony-arm64": "4.52.2",
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
"@rollup/rollup-win32-x64-gnu": "4.52.2",
"@rollup/rollup-win32-x64-msvc": "4.52.2",
"@rollup/rollup-android-arm-eabi": "4.52.3",
"@rollup/rollup-android-arm64": "4.52.3",
"@rollup/rollup-darwin-arm64": "4.52.3",
"@rollup/rollup-darwin-x64": "4.52.3",
"@rollup/rollup-freebsd-arm64": "4.52.3",
"@rollup/rollup-freebsd-x64": "4.52.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
"@rollup/rollup-linux-arm64-musl": "4.52.3",
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
"@rollup/rollup-linux-x64-gnu": "4.52.3",
"@rollup/rollup-linux-x64-musl": "4.52.3",
"@rollup/rollup-openharmony-arm64": "4.52.3",
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
"@rollup/rollup-win32-x64-gnu": "4.52.3",
"@rollup/rollup-win32-x64-msvc": "4.52.3",
"fsevents": "~2.3.2"
}
},
@@ -4201,9 +4247,9 @@
}
},
"node_modules/strip-literal": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
"integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4350,6 +4396,13 @@
"node": ">=6"
}
},
"node_modules/ts-algebra": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
"integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
"dev": true,
"license": "MIT"
},
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",

View File

@@ -6,7 +6,7 @@
"bin": {
"claude-code-acp": "./dist/index.js"
},
"version": "0.4.4",
"version": "0.4.7",
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
"main": "dist/index.js",
"type": "module",
@@ -51,15 +51,16 @@
"author": "Zed Industries",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/claude-code": "1.0.123",
"@modelcontextprotocol/sdk": "1.18.1",
"@zed-industries/agent-client-protocol": "0.4.2",
"@anthropic-ai/claude-code": "1.0.128",
"@modelcontextprotocol/sdk": "1.18.2",
"@zed-industries/agent-client-protocol": "0.4.3",
"diff": "8.0.2",
"express": "5.1.0",
"minimist": "1.2.8",
"uuid": "13.0.0"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.64.0",
"@types/express": "5.0.3",
"@types/minimist": "1.2.5",
"@types/node": "24.5.2",

View File

@@ -30,6 +30,7 @@ import {
query,
SDKAssistantMessage,
SDKUserMessage,
SDKUserMessageReplay,
} from "@anthropic-ai/claude-code";
import * as fs from "node:fs";
import * as path from "node:path";
@@ -44,7 +45,12 @@ import {
toolNames,
} from "./mcp-server.js";
import { AddressInfo } from "node:net";
import { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult } from "./tools.js";
import {
toolInfoFromToolUse,
planEntries,
toolUpdateFromToolResult,
ClaudePlanEntry,
} from "./tools.js";
type Session = {
query: Query;
@@ -270,6 +276,14 @@ export class ClaudeAcpAgent implements Agent {
}
switch (message.type) {
case "system":
switch (message.subtype) {
case "init":
break;
case "compact_boundary":
break;
default:
unreachable(message as never);
}
break;
case "result": {
if (this.sessions[params.sessionId].cancelled) {
@@ -292,12 +306,7 @@ export class ClaudeAcpAgent implements Agent {
return { stopReason: "refusal" };
}
}
case "user":
case "assistant": {
if (this.sessions[params.sessionId].cancelled) {
continue;
}
case "user": {
// Slash commands like /compact can generate invalid output... doesn't match
// their own docs: https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-slash-commands#%2Fcompact-compact-conversation-history
if (
@@ -315,14 +324,24 @@ export class ClaudeAcpAgent implements Agent {
console.error(message.message.content);
break;
}
// Skip user messages for now, since they seem to just be messages we don't want in the feed
break;
}
case "assistant": {
if (this.sessions[params.sessionId].cancelled) {
break;
}
if (
message.message.model === "<synthetic>" &&
Array.isArray(message.message.content) &&
message.message.content.length === 1 &&
message.message.content[0].type === "text" &&
message.message.content[0].text.includes("Please run /login")
) {
throw RequestError.authRequired();
}
for (const notification of toAcpNotifications(
message,
params.sessionId,
@@ -531,20 +550,38 @@ function promptToClaude(prompt: PromptRequest): SDKUserMessage {
* Only handles text, image, and thinking chunks for now.
*/
export function toAcpNotifications(
message: SDKAssistantMessage | SDKUserMessage,
message: SDKAssistantMessage | SDKUserMessage | SDKUserMessageReplay,
sessionId: string,
toolUseCache: ToolUseCache,
fileContentCache: { [key: string]: string },
): SessionNotification[] {
const chunks = message.message.content as ContentChunk[];
const content = message.message.content;
if (typeof content === "string") {
return [
{
sessionId,
update: {
sessionUpdate:
message.type === "assistant" ? "agent_message_chunk" : "user_message_chunk",
content: {
type: "text",
text: content,
},
},
},
];
}
const output = [];
// Only handle the first chunk for streaming; extend as needed for batching
for (const chunk of chunks) {
for (const chunk of content) {
let update: SessionNotification["update"] | null = null;
switch (chunk.type) {
case "text":
update = {
sessionUpdate: "agent_message_chunk",
sessionUpdate:
message.type === "assistant" ? "agent_message_chunk" : "user_message_chunk",
content: {
type: "text",
text: chunk.text,
@@ -553,7 +590,8 @@ export function toAcpNotifications(
break;
case "image":
update = {
sessionUpdate: "agent_message_chunk",
sessionUpdate:
message.type === "assistant" ? "agent_message_chunk" : "user_message_chunk",
content: {
type: "image",
data: chunk.source.type === "base64" ? chunk.source.data : "",
@@ -576,13 +614,19 @@ export function toAcpNotifications(
if (chunk.name === "TodoWrite") {
update = {
sessionUpdate: "plan",
entries: planEntries(chunk.input),
entries: planEntries(chunk.input as { todos: ClaudePlanEntry[] }),
};
} else {
let rawInput;
try {
rawInput = JSON.parse(JSON.stringify(chunk.input));
} catch {
// ignore if we can't turn it to JSON
}
update = {
toolCallId: chunk.id,
sessionUpdate: "tool_call",
rawInput: chunk.input,
rawInput,
status: "pending",
...toolInfoFromToolUse(chunk, fileContentCache),
};
@@ -627,24 +671,3 @@ export function runAcp() {
const stream = ndJsonStream(input, output);
new AgentSideConnection((client) => new ClaudeAcpAgent(client), stream);
}
type ContentChunk =
| { type: "text"; text: string }
| { type: "tool_use"; id: string; name: string; input: any }
| {
type: "tool_result";
content: string;
tool_use_id: string;
is_error: boolean;
} // content type depends on your Content definition
| { type: "thinking"; thinking: string }
| { type: "redacted_thinking" }
| { type: "image"; source: ImageSource }
| { type: "document" }
| { type: "web_search_tool_result" }
| { type: "untagged_text"; text: string };
// Example ImageSource type (adjust as needed)
type ImageSource =
| { type: "base64"; data: string; media_type: string }
| { type: "url"; url: string };

View File

@@ -102,15 +102,12 @@ In sessions with ${toolNames.read} always use it instead of Read as it contains
const content = await agent.readTextFile({
sessionId,
path: input.abs_path,
line: input.offset + 1,
limit: input.linesToRead,
});
// Extract lines with byte limit enforcement
const result = extractLinesWithByteLimit(
content.content,
input.offset ?? 0,
input.linesToRead,
defaults.maxFileSize,
);
const result = extractLinesWithByteLimit(content.content, defaults.maxFileSize);
// Construct informative message about what was read
let readInfo = "";
@@ -120,11 +117,11 @@ In sessions with ${toolNames.read} always use it instead of Read as it contains
if (result.wasLimited) {
readInfo += `Read ${result.linesRead} lines (hit 50KB limit). `;
} else {
readInfo += `Read lines ${(input.offset ?? 0) + 1}-${result.actualEndLine}. `;
readInfo += `Read lines ${(input.offset ?? 0) + 1}-${result.linesRead}. `;
}
if (result.wasLimited) {
readInfo += `Continue with offset=${result.actualEndLine}.`;
readInfo += `Continue with offset=${result.linesRead}.`;
}
readInfo += "</file-read-info>";

View File

@@ -576,6 +576,7 @@ describe("tool conversions", () => {
id: "msg_017eNosJgww7F5qD4a8BcAcx",
type: "message",
role: "assistant",
container: null,
model: "claude-sonnet-4-20250514",
content: [
{
@@ -640,6 +641,7 @@ describe("tool conversions", () => {
},
output_tokens: 1,
service_tier: "standard",
server_tool_use: null,
},
},
parent_tool_use_id: null,
@@ -778,7 +780,7 @@ describe("tool conversions", () => {
const toolResult = {
content: [
{
type: "text",
type: "text" as const,
text: "not valid json",
},
],
@@ -808,7 +810,7 @@ describe("tool conversions", () => {
const toolResult = {
content: [
{
type: "text",
type: "text" as const,
text: "Failed to find `old_string`",
},
],

View File

@@ -5,66 +5,44 @@ describe("extractLinesWithByteLimit", () => {
const simpleContent = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5";
it("should extract all lines when under byte limit", () => {
const result = extractLinesWithByteLimit(simpleContent, 0, 5, 1000);
const result = extractLinesWithByteLimit(simpleContent, 1000);
expect(result.content).toBe(simpleContent);
expect(result.actualEndLine).toBe(4);
expect(result.wasLimited).toBe(false);
expect(result.linesRead).toBe(5);
});
it("should extract partial lines with offset", () => {
const result = extractLinesWithByteLimit(simpleContent, 2, 2, 1000);
expect(result.content).toBe("Line 3\nLine 4");
expect(result.actualEndLine).toBe(3);
expect(result.wasLimited).toBe(false);
expect(result.linesRead).toBe(2);
});
it("should limit output when exceeding byte limit", () => {
// Create content that will exceed byte limit
const longLine = "x".repeat(100);
const manyLines = Array(10).fill(longLine).join("\n");
const result = extractLinesWithByteLimit(manyLines, 0, 10, 250);
const result = extractLinesWithByteLimit(manyLines, 250);
expect(result.wasLimited).toBe(true);
expect(result.actualEndLine).toBe(1); // Should only get 2 lines
expect(result.linesRead).toBe(2);
});
it("should handle offset beyond file length", () => {
const result = extractLinesWithByteLimit(simpleContent, 10, 5, 1000);
expect(result.content).toBe("");
expect(result.actualEndLine).toBe(10);
expect(result.wasLimited).toBe(false);
expect(result.linesRead).toBe(0);
});
it("should handle empty content", () => {
const result = extractLinesWithByteLimit("", 0, 10, 1000);
const result = extractLinesWithByteLimit("", 1000);
expect(result.content).toBe("");
expect(result.actualEndLine).toBe(0); // Empty string splits to [""] and we process that one empty line
expect(result.wasLimited).toBe(false);
expect(result.linesRead).toBe(1); // We read the one empty line
});
it("should handle single line file", () => {
const singleLine = "This is a single line without newline";
const result = extractLinesWithByteLimit(singleLine, 0, 10, 1000);
const result = extractLinesWithByteLimit(singleLine, 1000);
expect(result.content).toBe(singleLine);
expect(result.actualEndLine).toBe(0);
expect(result.wasLimited).toBe(false);
expect(result.linesRead).toBe(1);
});
it("should correctly count bytes with multi-byte characters", () => {
const unicodeContent = "Hello 世界\n你好 World\nEmoji: 🌍\nNormal line";
const result = extractLinesWithByteLimit(unicodeContent, 0, 10, 1000);
const result = extractLinesWithByteLimit(unicodeContent, 1000);
expect(result.content).toBe(unicodeContent);
expect(result.linesRead).toBe(4);
@@ -77,63 +55,25 @@ describe("extractLinesWithByteLimit", () => {
const line3 = "c".repeat(40);
const content = `${line1}\n${line2}\n${line3}`;
const result = extractLinesWithByteLimit(content, 0, 3, 85);
const result = extractLinesWithByteLimit(content, 85);
expect(result.content).toBe(`${line1}\n${line2}`);
expect(result.actualEndLine).toBe(1);
expect(result.content).toBe(`${line1}\n${line2}\n`);
expect(result.wasLimited).toBe(true);
expect(result.linesRead).toBe(2);
});
it("should read exactly to limit when possible", () => {
const exactContent = "12345\n67890\n12345"; // 17 bytes total
const result = extractLinesWithByteLimit(exactContent, 0, 3, 17);
const result = extractLinesWithByteLimit(exactContent, 17);
expect(result.content).toBe(exactContent);
expect(result.actualEndLine).toBe(2);
expect(result.wasLimited).toBe(false);
expect(result.linesRead).toBe(3);
});
it("should handle reading from middle to end", () => {
const result = extractLinesWithByteLimit(simpleContent, 3, 100, 1000);
expect(result.content).toBe("Line 4\nLine 5");
expect(result.actualEndLine).toBe(4);
expect(result.wasLimited).toBe(false);
expect(result.linesRead).toBe(2);
});
it("should handle large content with typical parameters", () => {
const longContent = Array(2000).fill("Line content here").join("\n");
const result = extractLinesWithByteLimit(longContent, 0, 1000, 50000);
expect(result.actualEndLine).toBeLessThanOrEqual(1000); // Should be limited by line count
});
it("should provide correct data for partial read at start", () => {
const result = extractLinesWithByteLimit(simpleContent, 0, 2, 1000);
expect(result.actualEndLine).toBe(1);
expect(result.linesRead).toBe(2);
});
it("should provide correct data for partial read with offset", () => {
const result = extractLinesWithByteLimit(simpleContent, 1, 2, 1000);
expect(result.actualEndLine).toBe(2);
expect(result.linesRead).toBe(2);
});
it("should not add newline after last line", () => {
const result = extractLinesWithByteLimit("Line 1\nLine 2\nLine 3", 2, 1, 1000);
expect(result.content).toBe("Line 3"); // No trailing newline
});
it("should handle Windows-style line endings", () => {
const windowsContent = "Line 1\r\nLine 2\r\nLine 3";
const result = extractLinesWithByteLimit(windowsContent, 0, 3, 1000);
const result = extractLinesWithByteLimit(windowsContent, 1000);
// Note: split("\n") will keep the \r characters
expect(result.content).toBe("Line 1\r\nLine 2\r\nLine 3");
@@ -145,29 +85,18 @@ describe("extractLinesWithByteLimit", () => {
const largeLine = "x".repeat(1000);
const largeContent = Array(110).fill(largeLine).join("\n");
const result = extractLinesWithByteLimit(largeContent, 0, 200, 50000);
const result = extractLinesWithByteLimit(largeContent, 50000);
expect(result.wasLimited).toBe(true);
expect(result.actualEndLine).toBeLessThan(50); // Should stop well before 200 lines
});
it("should handle line limit of 0", () => {
const result = extractLinesWithByteLimit(simpleContent, 0, 0, 1000);
expect(result.content).toBe("");
expect(result.actualEndLine).toBe(0);
expect(result.linesRead).toBe(0);
expect(result.wasLimited).toBe(false);
});
it("should allow at least one line even if it exceeds byte limit", () => {
const veryLongLine = "x".repeat(100000); // 100KB line
const result = extractLinesWithByteLimit(veryLongLine, 0, 1, 50000);
const result = extractLinesWithByteLimit(veryLongLine, 50000);
// Should return the line even though it exceeds the byte limit
// because we always allow at least one line if no lines have been added yet
expect(result.content).toBe(veryLongLine);
expect(result.actualEndLine).toBe(0);
expect(result.linesRead).toBe(1);
expect(result.wasLimited).toBe(false);
});

View File

@@ -5,6 +5,7 @@ import {
ToolKind,
} from "@zed-industries/agent-client-protocol";
import { replaceAndCalculateLocation, SYSTEM_REMINDER, toolNames } from "./mcp-server.js";
import { ToolResultBlockParam } from "@anthropic-ai/sdk/resources";
interface ToolInfo {
title: string;
@@ -410,7 +411,13 @@ export function toolInfoFromToolUse(
: [],
};
case "Other":
case "Other": {
let output;
try {
output = JSON.stringify(input, null, 2);
} catch {
output = typeof input === "string" ? input : "{}";
}
return {
title: name || "Unknown Tool",
kind: "other",
@@ -419,11 +426,12 @@ export function toolInfoFromToolUse(
type: "content",
content: {
type: "text",
text: `\`\`\`json\n${JSON.stringify(input, null, 2) || "{}"}\`\`\``,
text: `\`\`\`json\n${output}\`\`\``,
},
},
],
};
}
default:
return {
@@ -434,15 +442,8 @@ export function toolInfoFromToolUse(
}
}
type ToolResult = {
type: "tool_result";
content: any;
tool_use_id: string;
is_error: boolean;
};
export function toolUpdateFromToolResult(
toolResult: ToolResult,
toolResult: ToolResultBlockParam,
toolUse: any | undefined,
): ToolUpdate {
switch (toolUse?.name) {
@@ -485,7 +486,7 @@ export function toolUpdateFromToolResult(
case "MultiEdit":
case toolNames.write:
case "Write": {
if (toolResult.is_error && toolResult.content?.length > 0) {
if (toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
// Only return errors
return toAcpContentUpdate(toolResult.content, true);
}
@@ -549,7 +550,7 @@ function toAcpContentUpdate(
return {};
}
type ClaudePlanEntry = {
export type ClaudePlanEntry = {
content: string;
status: "pending" | "in_progress" | "completed";
activeForm: string;

View File

@@ -125,7 +125,6 @@ export function applyEnvironmentSettings(settings: ManagedSettings): void {
export interface ExtractLinesResult {
content: string;
actualEndLine: number;
wasLimited: boolean;
linesRead: number;
}
@@ -134,100 +133,55 @@ export interface ExtractLinesResult {
* Extracts lines from file content with byte limit enforcement.
*
* @param fullContent - The complete file content
* @param linesToSkip - Starting line number (0-based)
* @param linesToRead - Maximum number of lines to read
* @param maxContentLength - Maximum number of UTF-16 Code Units to return
* @returns Object containing extracted content and metadata
*/
export function extractLinesWithByteLimit(
fullContent: string,
linesToSkip: number,
linesToRead: number,
maxContentLength: number,
): ExtractLinesResult {
if (fullContent === "" || linesToRead === 0) {
if (linesToSkip === 0 && linesToRead > 0) {
return {
content: "",
actualEndLine: 0,
wasLimited: false,
linesRead: 1,
};
} else {
return {
content: "",
actualEndLine: linesToSkip,
wasLimited: false,
linesRead: 0,
};
}
if (fullContent === "") {
return {
content: "",
wasLimited: false,
linesRead: 1,
};
}
let linesSeen = 0;
let index = 0;
while (linesSeen < linesToSkip) {
const nextIndex = fullContent.indexOf("\n", index);
// There were not enough lines to skip.
if (nextIndex < 0) {
return {
content: "",
actualEndLine: linesToSkip,
wasLimited: false,
linesRead: 0,
};
}
linesSeen += 1;
index = nextIndex + 1;
}
// We've successfully skipped over all the lines we were supposed to.
// Now we can actually start reading!
const startIndex = index;
linesSeen = 0;
let contentLength = 0;
let wasLimited = false;
while (linesSeen < linesToRead) {
while (true) {
const nextIndex = fullContent.indexOf("\n", index);
if (nextIndex < 0) {
// Last line in file (no trailing newline)
const newContentLength = fullContent.length - startIndex;
if (linesSeen > 0 && newContentLength > maxContentLength) {
if (linesSeen > 0 && fullContent.length > maxContentLength) {
wasLimited = true;
break;
}
linesSeen += 1;
contentLength = newContentLength;
contentLength = fullContent.length;
break;
} else {
// Line with newline - include up to the newline
const newContentLength = nextIndex + 1 - startIndex;
const newContentLength = nextIndex + 1;
if (linesSeen > 0 && newContentLength > maxContentLength) {
wasLimited = true;
break;
}
linesSeen += 1;
contentLength = newContentLength;
index = nextIndex + 1;
}
}
// If we ended with a newline and we stopped due to byte limit or line limit (not end of file), remove the trailing newline
if (contentLength > 0 && fullContent[startIndex + contentLength - 1] === "\n") {
// Check if there's more content after our current position that we didn't read
if (startIndex + contentLength < fullContent.length) {
contentLength -= 1;
index = newContentLength;
}
}
return {
content: fullContent.slice(startIndex, startIndex + contentLength),
actualEndLine: linesSeen > 0 ? linesToSkip + linesSeen - 1 : linesToSkip,
content: fullContent.slice(0, contentLength),
wasLimited,
linesRead: linesSeen,
};