Compare commits

..

3 Commits

Author SHA1 Message Date
Conrad Irwin
532daf0e7e v0.1.8 2025-09-02 12:33:40 -07:00
Conrad Irwin
d6e5910631 Fix load errors when SDK is busted 2025-09-02 12:33:11 -07:00
Agus Zubiaga
0d5e78f1ac Fix info for builtin tools (#11) 2025-09-02 15:50:21 -03:00
6 changed files with 62 additions and 41 deletions

View File

@@ -6,7 +6,7 @@
"bin": {
"claude-code-acp": "./dist/index.js"
},
"version": "0.1.7",
"version": "0.1.8",
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
"main": "dist/index.js",
"type": "module",

View File

@@ -27,11 +27,15 @@ import {
SDKAssistantMessage,
SDKUserMessage,
} from "@anthropic-ai/claude-code";
import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { v7 as uuidv7 } from "uuid";
import {
nodeToWebReadable,
nodeToWebWritable,
Pushable,
sleep,
unreachable,
} from "./utils.js";
import { SessionNotification } from "@zed-industries/agent-client-protocol";
@@ -164,7 +168,7 @@ export class ClaudeAcpAgent implements Agent {
options.disallowedTools = disallowedTools;
}
let q = query({
const q = query({
prompt: input,
options,
});
@@ -320,8 +324,20 @@ async function availableSlashCommands(
"vim", // Not needed
];
//todo: Do not use `as any` once `supportedCommands` is exposed via the typescript interface
const commands = await (query as any).supportedCommands();
const commands = await Promise.race([
//todo: Do not use `as any` once `supportedCommands` is exposed via the typescript interface
(query as any).supportedCommands(),
sleep(5000).then(() => {
if (
fs.existsSync(path.resolve(os.homedir(), ".claude.json.backup")) &&
!fs.existsSync(path.resolve(os.homedir(), ".claude.json"))
) {
throw RequestError.authRequired();
}
throw new Error("can't load supported slash commands");
}),
]);
return commands
.map(
(command: {

View File

@@ -10,6 +10,7 @@ import {
TerminalOutputResponse,
} from "@zed-industries/agent-client-protocol";
import { tool } from "@anthropic-ai/claude-code";
import { sleep } from "./utils.js";
export const SYSTEM_REMINDER = `
@@ -171,9 +172,9 @@ In sessions with mcp__acp__edit always use it instead of Edit as it will
allow the user to conveniently review changes.
File editing instructions:
- The \`old_text\` param must match existing file content, including indentation.
- The \`old_text\` param must come from the actual file, not an outline.
- The \`old_text\` section must not be empty.
- The \`old_string\` param must match existing file content, including indentation.
- The \`old_string\` param must come from the actual file, not an outline.
- The \`old_string\` section must not be empty.
- Be minimal with replacements:
- For unique lines, include only those lines.
- For non-unique lines, include enough context to identify them.
@@ -183,10 +184,10 @@ File editing instructions:
abs_path: z
.string()
.describe("The absolute path to the file to read."),
old_text: z
old_string: z
.string()
.describe("The old text to replace (must be unique in the file)"),
new_text: z.string().describe("The new text."),
new_string: z.string().describe("The new text."),
},
annotations: {
title: "Edit file",
@@ -219,8 +220,8 @@ File editing instructions:
content,
[
{
oldText: input.old_text,
newText: input.new_text,
oldText: input.old_string,
newText: input.new_string,
replaceAll: false,
},
],
@@ -598,10 +599,6 @@ File editing instructions:
return b.slice(i);
}
function sleep(time: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, time));
}
function toolCommandOutput(
status: "started" | "aborted" | "exited" | "killed" | "timedOut",
output: TerminalOutputResponse,

View File

@@ -309,7 +309,7 @@ describe("tool conversions", () => {
id: "toolu_01ABC123XYZ789",
name: "Write",
input: {
abs_path: "/Users/test/project/example.txt",
file_path: "/Users/test/project/example.txt",
content: "Hello, World!\nThis is test content.",
},
};
@@ -361,7 +361,7 @@ describe("tool conversions", () => {
id: "toolu_01MNO456PQR789",
name: "Read",
input: {
abs_path: "/Users/test/project/readme.md",
file_path: "/Users/test/project/readme.md",
},
};
@@ -664,8 +664,8 @@ describe("tool conversions", () => {
name: "edit",
input: {
abs_path: "/Users/test/project/example.txt",
old_text: "old content",
new_text: "new content",
old_string: "old content",
new_string: "new content",
},
};
@@ -795,8 +795,8 @@ describe("tool conversions", () => {
name: "mcp__acp__edit",
input: {
abs_path: "/Users/test/project/main.py",
old_text: "def hello():",
new_text: "def hello_world():",
old_string: "def hello():",
new_string: "def hello_world():",
},
};
@@ -823,8 +823,8 @@ describe("tool conversions", () => {
name: "edit",
input: {
abs_path: "/Users/test/project/notfound.txt",
old_text: "nonexistent",
new_text: "replacement",
old_string: "nonexistent",
new_string: "replacement",
},
};
@@ -849,8 +849,8 @@ describe("tool conversions", () => {
name: "edit",
input: {
abs_path: "/Users/test/project/test.txt",
old_text: "old",
new_text: "new",
old_string: "old",
new_string: "new",
},
};

View File

@@ -137,7 +137,7 @@ export function toolInfoFromToolUse(
title: "Read File",
kind: "read",
content: [],
locations: [{ path: input.abs_path, line: input.offset ?? 0 }],
locations: [{ path: input.file_path, line: input.offset ?? 0 }],
};
case "LS":
@@ -149,42 +149,46 @@ export function toolInfoFromToolUse(
};
case "mcp__acp__edit":
case "Edit":
let oldText = input.old_text || null;
let newText = input.new_text;
case "Edit": {
const path = input?.abs_path ?? input?.file_path;
let oldText = input.old_string ?? null;
let newText = input.new_string ?? "";
let line = null;
if (input?.abs_path && input.old_text) {
if (path && input.old_text) {
try {
let oldContent = cachedFileContent[input.abs_path] || "";
let oldContent = cachedFileContent[path] || "";
let newContent = replaceAndCalculateLocation(oldContent, [
{
oldText: input.old_text,
newText: input.new_text,
oldText: input.old_string,
newText: input.new_string,
replaceAll: false,
},
]);
oldText = oldContent;
newText = newContent.newContent;
line = newContent.lineNumbers[0];
} catch (e) {
console.error(e);
}
}
return {
title: input?.abs_path ? `Edit ${input.abs_path}` : "Edit",
title: path ? `Edit ${path}` : "Edit",
kind: "edit",
content:
input && input.abs_path
input && path
? [
{
type: "diff",
path: input.abs_path,
path,
oldText,
newText,
},
]
: [],
locations: input?.abs_path ? [{ path: input.abs_path }] : [],
locations: path ? [{ path, line }] : [],
};
}
case "mcp__acp__multi-edit":
case "MultiEdit":
@@ -265,20 +269,20 @@ export function toolInfoFromToolUse(
case "Write":
return {
title: input?.abs_path ? `Write ${input.abs_path}` : "Write",
title: input?.file_path ? `Write ${input.file_path}` : "Write",
kind: "edit",
content:
input && input.abs_path
input && input.file_path
? [
{
type: "diff",
path: input.abs_path,
path: input.file_path,
oldText: null,
newText: input.content,
},
]
: [],
locations: input?.abs_path ? [{ path: input.abs_path }] : [],
locations: input?.file_path ? [{ path: input.file_path }] : [],
};
case "Glob": {

View File

@@ -77,3 +77,7 @@ export function nodeToWebReadable(
export function unreachable(value: never): never {
throw new Error(`Unexpected case: ${value}`);
}
export function sleep(time: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, time));
}