mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-28 04:51:50 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48df285bd0 | ||
|
|
b138c84cdb | ||
|
|
0735aca015 | ||
|
|
847d62dacb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## 0.9.0
|
||||
|
||||
- Support slash commands coming from MCP servers (Prompts)
|
||||
|
||||
## 0.8.0
|
||||
|
||||
- Revert changes to filename for cli entrypoint
|
||||
- Provide library entrypoint via lib.ts
|
||||
|
||||
## 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.
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@zed-industries/claude-code-acp",
|
||||
"version": "0.7.0",
|
||||
"version": "0.9.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@zed-industries/claude-code-acp",
|
||||
"version": "0.7.0",
|
||||
"version": "0.9.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.5.1",
|
||||
@@ -19,7 +19,7 @@
|
||||
"uuid": "13.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-code-acp": "dist/cli.js"
|
||||
"claude-code-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.67.0",
|
||||
|
||||
12
package.json
12
package.json
@@ -3,12 +3,12 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"bin": {
|
||||
"claude-code-acp": "./dist/cli.js"
|
||||
},
|
||||
"version": "0.7.0",
|
||||
"version": "0.9.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
|
||||
"main": "dist/index.js",
|
||||
"main": "dist/lib.js",
|
||||
"bin": {
|
||||
"claude-code-acp": "./dist/index.js"
|
||||
},
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist/",
|
||||
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/cli.js",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "npm run build && npm run start",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint:fix": "eslint src --ext .ts --fix",
|
||||
|
||||
@@ -515,54 +515,30 @@ async function getAvailableModels(query: Query): Promise<SessionModelState> {
|
||||
|
||||
async function getAvailableSlashCommands(query: Query): Promise<AvailableCommand[]> {
|
||||
const UNSUPPORTED_COMMANDS = [
|
||||
"add-dir",
|
||||
"agents", // Modal
|
||||
"bashes", // Modal
|
||||
"bug", // Modal
|
||||
"clear", // Escape Codes
|
||||
"config", // Modal
|
||||
"context", // Escape Codes
|
||||
"cost", // Escape Codes
|
||||
"doctor", // Escape Codes
|
||||
"exit",
|
||||
"export", // Modal
|
||||
"help", // Modal
|
||||
"hooks", // Modal
|
||||
"ide", // Modal
|
||||
"install-github-app", // Modal
|
||||
"context",
|
||||
"cost",
|
||||
"login",
|
||||
"logout",
|
||||
"memory",
|
||||
"mcp",
|
||||
"migrate-installer", // Modal
|
||||
"model", // Not supported via SDK?
|
||||
"output-style", // Modal
|
||||
"output-style:new", // Modal
|
||||
"permissions", // Modal
|
||||
"privacy-settings",
|
||||
"release-notes", // Escape Codes
|
||||
"resume",
|
||||
"status", // Not supported via SDK?
|
||||
"statusline", // Not needed
|
||||
"terminal-setup", // Not needed
|
||||
"todos", // Escape Codes
|
||||
"vim", // Not needed
|
||||
"output-style:new",
|
||||
"release-notes",
|
||||
"todos",
|
||||
];
|
||||
const commands = await query.supportedCommands();
|
||||
|
||||
return commands
|
||||
.map((command) => {
|
||||
const input = command.argumentHint ? { hint: command.argumentHint } : null;
|
||||
let name = command.name;
|
||||
if (command.name.endsWith(" (MCP)")) {
|
||||
name = `mcp:${name.replace(" (MCP)", "")}`;
|
||||
}
|
||||
return {
|
||||
name: command.name,
|
||||
name,
|
||||
description: command.description || "",
|
||||
input,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(command: AvailableCommand) =>
|
||||
!(command.name.match(/\(MCP\)/) || UNSUPPORTED_COMMANDS.includes(command.name)),
|
||||
);
|
||||
.filter((command: AvailableCommand) => !UNSUPPORTED_COMMANDS.includes(command.name));
|
||||
}
|
||||
|
||||
function formatUriAsLink(uri: string): string {
|
||||
@@ -582,15 +558,23 @@ function formatUriAsLink(uri: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function promptToClaude(prompt: PromptRequest): SDKUserMessage {
|
||||
export function promptToClaude(prompt: PromptRequest): SDKUserMessage {
|
||||
const content: any[] = [];
|
||||
const context: any[] = [];
|
||||
|
||||
for (const chunk of prompt.prompt) {
|
||||
switch (chunk.type) {
|
||||
case "text":
|
||||
content.push({ type: "text", text: chunk.text });
|
||||
case "text": {
|
||||
let text = chunk.text;
|
||||
// change /mcp:server:command args -> /server:command (MCP) args
|
||||
const mcpMatch = text.match(/^\/mcp:([^:\s]+):(\S+)(\s+.*)?$/);
|
||||
if (mcpMatch) {
|
||||
const [, server, command, args] = mcpMatch;
|
||||
text = `/${server}:${command} (MCP)${args || ""}`;
|
||||
}
|
||||
content.push({ type: "text", text });
|
||||
break;
|
||||
}
|
||||
case "resource_link": {
|
||||
const formattedUri = formatUriAsLink(chunk.uri);
|
||||
content.push({
|
||||
|
||||
26
src/cli.ts
26
src/cli.ts
@@ -1,26 +0,0 @@
|
||||
#!/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();
|
||||
49
src/index.ts
49
src/index.ts
@@ -1,25 +1,26 @@
|
||||
// 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";
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Export types
|
||||
export type { ClaudePlanEntry } 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 } from "./acp-agent.js";
|
||||
runAcp();
|
||||
|
||||
// Keep process alive
|
||||
process.stdin.resume();
|
||||
|
||||
25
src/lib.ts
Normal file
25
src/lib.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// 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";
|
||||
|
||||
// Export types
|
||||
export type { ClaudePlanEntry } from "./tools.js";
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@agentclientprotocol/sdk";
|
||||
import { nodeToWebWritable, nodeToWebReadable } from "../utils.js";
|
||||
import { markdownEscape, toolInfoFromToolUse, toolUpdateFromToolResult } from "../tools.js";
|
||||
import { toAcpNotifications } from "../acp-agent.js";
|
||||
import { toAcpNotifications, promptToClaude } from "../acp-agent.js";
|
||||
import { query, SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration", () => {
|
||||
@@ -783,6 +783,44 @@ describe("escape markdown", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("prompt conversion", () => {
|
||||
it("should not change built-in slash commands", () => {
|
||||
const message = promptToClaude({
|
||||
sessionId: "test",
|
||||
prompt: [
|
||||
{
|
||||
type: "text",
|
||||
text: "/compact args",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(message.message.content).toEqual([
|
||||
{
|
||||
text: "/compact args",
|
||||
type: "text",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should remove MCP prefix from MCP slash commands", () => {
|
||||
const message = promptToClaude({
|
||||
sessionId: "test",
|
||||
prompt: [
|
||||
{
|
||||
type: "text",
|
||||
text: "/mcp:server:name args",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(message.message.content).toEqual([
|
||||
{
|
||||
text: "/server:name (MCP) args",
|
||||
type: "text",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SDK behavior", () => {
|
||||
it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
|
||||
"query has a 'default' model",
|
||||
|
||||
Reference in New Issue
Block a user