Compare commits

...

4 Commits

Author SHA1 Message Date
Ben Brandt
becf1d4f0a Update to claude agent sdk 0.1.27 (#125)
Also simplifies the permission mode workaround so we don't have to
change the profile immediately, which could have raced with client
changes anwyay.
2025-10-27 12:34:43 +01:00
Guillaume Sabran
5491bb8dcc Allow for the package to be used as a dependency, while preserving existing behavior when run (#124)
This change will allow for this package to be used directly, without
launching a subprocess and communicating over stdio.

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-10-27 11:10:41 +00:00
Ben Brandt
17aad5b7a1 Provide agentInfo on initialization response (#123)
- **Provide agentInfo on initialization**
- **Prep 0.6.10**
2025-10-24 17:02:07 +00:00
Steffen Deusch
dadc7f9f40 prevent invalid locations when read call has no path (#121)
The ACP spec has the path as a required field for tool call locations,
but I've seen Claude generate read calls without an input path, which
would generate:

```json
{
  "content": [],
  "kind": "read",
  "locations": [
    {
      "line": 0
    }
  ],
  "rawInput": {},
  "sessionUpdate": "tool_call",
  "status": "pending",
  "title": "Read File",
  "toolCallId": "toolu_012PzeR5whUjWN4ij2HBubJw"
}
```

And then fail to parse as SessionNotification in the SDK.
2025-10-24 12:49:43 +00:00
7 changed files with 153 additions and 112 deletions

View File

@@ -1,5 +1,17 @@
# Changelog
## 0.7.0
- Allow importing from this package as a library in addition to running it as a CLI. Allows for easier integration into existing node applications.
- Update to @anthropic-ai/claude-agent-sdk@v0.1.27
## 0.6.10
- Provide `agentInfo` on initialization response.
- Update to @agentclientprotocol/sdk@0.5.1
- Fix crash when receiving a hook_response event
- Fix for invalid locations when read call has no path
## 0.6.9
- Update to @anthropic-ai/claude-agent-sdk@v0.1.26

128
package-lock.json generated
View File

@@ -1,17 +1,17 @@
{
"name": "@zed-industries/claude-code-acp",
"version": "0.6.9",
"version": "0.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@zed-industries/claude-code-acp",
"version": "0.6.9",
"version": "0.7.0",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.5.0",
"@anthropic-ai/claude-agent-sdk": "0.1.26",
"@anthropic-ai/claude-code": "2.0.26",
"@agentclientprotocol/sdk": "0.5.1",
"@anthropic-ai/claude-agent-sdk": "0.1.27",
"@anthropic-ai/claude-code": "2.0.27",
"@modelcontextprotocol/sdk": "1.20.2",
"diff": "8.0.2",
"express": "5.1.0",
@@ -19,39 +19,39 @@
"uuid": "13.0.0"
},
"bin": {
"claude-code-acp": "dist/index.js"
"claude-code-acp": "dist/cli.js"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.67.0",
"@anthropic-ai/sdk": "0.67.0",
"@types/express": "5.0.4",
"@types/minimist": "1.2.5",
"@types/node": "24.9.1",
"@types/uuid": "10.0.0",
"@typescript-eslint/eslint-plugin": "8.46.2",
"@typescript-eslint/parser": "8.46.2",
"@vitest/ui": "4.0.2",
"@vitest/ui": "4.0.3",
"eslint": "9.38.0",
"eslint-config-prettier": "10.1.8",
"globals": "16.4.0",
"prettier": "3.6.2",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vitest": "4.0.2"
"vitest": "4.0.3"
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.5.0.tgz",
"integrity": "sha512-4TJcKRoXzmWu+pr8SkcUJAJaOlIjUzBcseJSQHiqQ9kC1CfSiSKyRrlYvWYT7Zd63KVDWUZ6Juk45Iku9s3Ryg==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.5.1.tgz",
"integrity": "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg==",
"license": "Apache-2.0",
"dependencies": {
"zod": "^3.0.0"
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.26.tgz",
"integrity": "sha512-daVGe5x6KOFy9DKyDojw15eljBP+tPx+spAyGll/Rp/B6t+NP0Oiwpaiib0afjbCRbqXkWEHUJPE3yDFrMcZlA==",
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.27.tgz",
"integrity": "sha512-HuMPW6spj2q8FODiP/WBCqUZAYGwDPoI1EpicP9KUXvuYk+2MZQYSaD7oiN6iNPupR2T5oJ2HY/D9OzjyCD2Mw==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.0.0"
@@ -69,9 +69,9 @@
}
},
"node_modules/@anthropic-ai/claude-code": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.0.26.tgz",
"integrity": "sha512-bk/sxGdTEbI0N7g+pTwYq5CNUjdAcvIKXvf/x5oRV9q24b42uq3gijrZGycSbWmtYTIjbzFAHeWw5n7Ic43/6w==",
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.0.27.tgz",
"integrity": "sha512-Ml4tnBeJu+K7MUFLTg8q7U2BE39Vx2D+KI7t86QnFX/K90c58ekFDUUDnfAp1PqTe9vRms6vARJiWzNxrePinA==",
"license": "SEE LICENSE IN README.md",
"bin": {
"claude": "cli.js"
@@ -1875,16 +1875,16 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.2.tgz",
"integrity": "sha512-izQY+ABWqL2Vyr5+LNo3m16nLLTAzLn8em6i5uxqsrWRhdgzdN5JIHrpFVGBAYRGDAbtwE+yD4Heu8gsBSWTVQ==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.3.tgz",
"integrity": "sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.0.2",
"@vitest/utils": "4.0.2",
"@vitest/spy": "4.0.3",
"@vitest/utils": "4.0.3",
"chai": "^6.0.1",
"tinyrainbow": "^3.0.3"
},
@@ -1893,13 +1893,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.2.tgz",
"integrity": "sha512-oiny+oBSGU9vHMA1DPdO+t1GVidCRuA4lKSG6rbo5SrCiTCGl7bTCyTaUkwxDpUkiSxEVneeXW4LJ4fg3H56dw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.3.tgz",
"integrity": "sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.0.2",
"@vitest/spy": "4.0.3",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.19"
},
@@ -1920,9 +1920,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.2.tgz",
"integrity": "sha512-PhrSiljryCz5nUDhHla5ihXYy2iRCBob+rNqlu34dA+KZIllVR39rUGny5R3kLgDgw3r8GW1ptOo64WbieMkeQ==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.3.tgz",
"integrity": "sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1933,13 +1933,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.2.tgz",
"integrity": "sha512-mPS5T/ZDuO6J5rsQiA76CFmlHtos7dnCvL14I1Oo8SbcjIhJd6kirFmekovfYLRygdF0gJe6SA5asCKIWKw1tw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.3.tgz",
"integrity": "sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.0.2",
"@vitest/utils": "4.0.3",
"pathe": "^2.0.3"
},
"funding": {
@@ -1947,13 +1947,13 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.2.tgz",
"integrity": "sha512-NibujZAh+fTQlpGdP8J2pZcsPg7EPjiLUOUq9In++4p35vc9xIFMkXfQDbBSpijqZPe6i2hEKrUCbKu70/sPzw==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.3.tgz",
"integrity": "sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.2",
"@vitest/pretty-format": "4.0.3",
"magic-string": "^0.30.19",
"pathe": "^2.0.3"
},
@@ -1962,9 +1962,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.2.tgz",
"integrity": "sha512-KrTWRXFPYrbhD0iUXeoA8BMXl81nvemj5D8sc7NbTlRvCeUWo36JheOWtAUCafcNi0G72ycAdsvWQVSOxy/3TA==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.3.tgz",
"integrity": "sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1972,13 +1972,13 @@
}
},
"node_modules/@vitest/ui": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.2.tgz",
"integrity": "sha512-GqPobLaUnKxkEJQHxszJ4yTSHCNGysWto6wANoBn/iXWU1juZV5pVjopxu+DkMKU+J6UQqvZ4ZXkcjxzRQS31A==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.3.tgz",
"integrity": "sha512-HURRrgGVzz2GQ2Imurp55FA+majHXgCXMzcwtojUZeRsAXyHNgEvxGRJf4QQY4kJeVakiugusGYeUqBgZ/xylg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.0.2",
"@vitest/utils": "4.0.3",
"fflate": "^0.8.2",
"flatted": "^3.3.3",
"pathe": "^2.0.3",
@@ -1990,17 +1990,17 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"vitest": "4.0.2"
"vitest": "4.0.3"
}
},
"node_modules/@vitest/utils": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.2.tgz",
"integrity": "sha512-H9jFzZb/5B5Qh7ajPUWMJ8UYGxQ4EQTaNLSm3icXs/oXkzQ1jqfcWDEJ4U3LkFPZOd6QW8M2MYjz32poW+KKqg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.3.tgz",
"integrity": "sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.2",
"@vitest/pretty-format": "4.0.3",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -4615,19 +4615,19 @@
}
},
"node_modules/vitest": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.2.tgz",
"integrity": "sha512-SXrA2ZzOPulX479d8W13RqKSmvHb9Bfg71eW7Fbs6ZjUFcCCXyt/OzFCkNyiUE8mFlPHa4ZVUGw0ky+5ndKnrg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.3.tgz",
"integrity": "sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.0.2",
"@vitest/mocker": "4.0.2",
"@vitest/pretty-format": "4.0.2",
"@vitest/runner": "4.0.2",
"@vitest/snapshot": "4.0.2",
"@vitest/spy": "4.0.2",
"@vitest/utils": "4.0.2",
"@vitest/expect": "4.0.3",
"@vitest/mocker": "4.0.3",
"@vitest/pretty-format": "4.0.3",
"@vitest/runner": "4.0.3",
"@vitest/snapshot": "4.0.3",
"@vitest/spy": "4.0.3",
"@vitest/utils": "4.0.3",
"debug": "^4.4.3",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
@@ -4655,10 +4655,10 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.0.2",
"@vitest/browser-preview": "4.0.2",
"@vitest/browser-webdriverio": "4.0.2",
"@vitest/ui": "4.0.2",
"@vitest/browser-playwright": "4.0.3",
"@vitest/browser-preview": "4.0.3",
"@vitest/browser-webdriverio": "4.0.3",
"@vitest/ui": "4.0.3",
"happy-dom": "*",
"jsdom": "*"
},

View File

@@ -4,9 +4,9 @@
"access": "public"
},
"bin": {
"claude-code-acp": "./dist/index.js"
"claude-code-acp": "./dist/cli.js"
},
"version": "0.6.9",
"version": "0.7.0",
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
"main": "dist/index.js",
"type": "module",
@@ -18,7 +18,7 @@
],
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"start": "node dist/cli.js",
"dev": "npm run build && npm run start",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix",
@@ -51,9 +51,9 @@
"author": "Zed Industries",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.5.0",
"@anthropic-ai/claude-agent-sdk": "0.1.26",
"@anthropic-ai/claude-code": "2.0.26",
"@agentclientprotocol/sdk": "0.5.1",
"@anthropic-ai/claude-agent-sdk": "0.1.27",
"@anthropic-ai/claude-code": "2.0.27",
"@modelcontextprotocol/sdk": "1.20.2",
"diff": "8.0.2",
"express": "5.1.0",
@@ -61,20 +61,20 @@
"uuid": "13.0.0"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.67.0",
"@anthropic-ai/sdk": "0.67.0",
"@types/express": "5.0.4",
"@types/minimist": "1.2.5",
"@types/node": "24.9.1",
"@types/uuid": "10.0.0",
"@typescript-eslint/eslint-plugin": "8.46.2",
"@typescript-eslint/parser": "8.46.2",
"@vitest/ui": "4.0.2",
"@vitest/ui": "4.0.3",
"eslint": "9.38.0",
"eslint-config-prettier": "10.1.8",
"globals": "16.4.0",
"prettier": "3.6.2",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vitest": "4.0.2"
"vitest": "4.0.3"
}
}

View File

@@ -55,6 +55,7 @@ import {
} from "./tools.js";
import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
import packageJson from "../package.json" with { type: "json" };
type Session = {
query: Query;
@@ -110,7 +111,6 @@ export class ClaudeAcpAgent implements Agent {
this.clientCapabilities = request.clientCapabilities;
return {
protocolVersion: 1,
// todo!()
agentCapabilities: {
promptCapabilities: {
image: true,
@@ -121,6 +121,11 @@ export class ClaudeAcpAgent implements Agent {
sse: true,
},
},
agentInfo: {
name: packageJson.name,
title: "Claude Code",
version: packageJson.version,
},
authMethods: [
{
description: "Run `claude /login` in the terminal",
@@ -205,9 +210,10 @@ export class ClaudeAcpAgent implements Agent {
mcpServers,
systemPrompt,
settingSources: ["user", "project", "local"],
// If we want bypassPermissions to be an option, we have to start the session with it on.
// We change it immediately back to default below before returning the session.
permissionMode: IS_ROOT ? permissionMode : "bypassPermissions",
// If we want bypassPermissions to be an option, we have to allow it here.
// But it doesn't work in root mode, so we only activate it if it will work.
allowDangerouslySkipPermissions: !IS_ROOT,
permissionMode,
permissionPromptToolName: PERMISSION_TOOL_NAME,
stderr: (err) => console.error(err),
// note: although not documented by the types, passing an absolute path
@@ -253,10 +259,6 @@ export class ClaudeAcpAgent implements Agent {
const availableCommands = await getAvailableSlashCommands(q);
const models = await getAvailableModels(q);
// Change it back to default
if (!IS_ROOT) {
await q.setPermissionMode(permissionMode);
}
// Needs to happen after we return the session
setTimeout(() => {

26
src/cli.ts Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
// Load managed settings and apply environment variables
import { loadManagedSettings, applyEnvironmentSettings } from "./utils.js";
const managedSettings = loadManagedSettings();
if (managedSettings) {
applyEnvironmentSettings(managedSettings);
}
// stdout is used to send messages to the client
// we redirect everything else to stderr to make sure it doesn't interfere with ACP
console.log = console.error;
console.info = console.error;
console.warn = console.error;
console.debug = console.error;
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled Rejection at:", promise, "reason:", reason);
});
import { runAcp } from "./acp-agent.js";
runAcp();
// Keep process alive
process.stdin.resume();

View File

@@ -1,26 +1,25 @@
#!/usr/bin/env node
// Export the main agent class and utilities for library usage
export {
ClaudeAcpAgent,
runAcp,
toAcpNotifications,
streamEventToAcpNotifications,
} from "./acp-agent.js";
export {
loadManagedSettings,
applyEnvironmentSettings,
nodeToWebReadable,
nodeToWebWritable,
Pushable,
unreachable,
} from "./utils.js";
export {
createMcpServer,
createPermissionMcpServer,
PERMISSION_TOOL_NAME,
toolNames,
} from "./mcp-server.js";
export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult } from "./tools.js";
// Load managed settings and apply environment variables
import { loadManagedSettings, applyEnvironmentSettings } from "./utils.js";
const managedSettings = loadManagedSettings();
if (managedSettings) {
applyEnvironmentSettings(managedSettings);
}
// stdout is used to send messages to the client
// we redirect everything else to stderr to make sure it doesn't interfere with ACP
console.log = console.error;
console.info = console.error;
console.warn = console.error;
console.debug = console.error;
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled Rejection at:", promise, "reason:", reason);
});
import { runAcp as runAcp } from "./acp-agent.js";
runAcp();
// Keep process alive
process.stdin.resume();
// Export types
export type { ClaudePlanEntry } from "./tools.js";

View File

@@ -113,12 +113,14 @@ export function toolInfoFromToolUse(
return {
title: "Read " + (input.file_path ?? "File") + limit,
kind: "read",
locations: [
{
path: input.file_path,
line: input.offset ?? 0,
},
],
locations: input.file_path
? [
{
path: input.file_path,
line: input.offset ?? 0,
},
]
: [],
content: [],
};
}