Compare commits

...

3 Commits

Author SHA1 Message Date
Bennet Bo Fenner
48df285bd0 Prep v0.9.0 (#134) 2025-10-31 16:50:55 +01:00
Bennet Bo Fenner
b138c84cdb Support slash commands coming from MCP prompts (#133)
Closes #115. Thanks to @joeljohansson99 for reporting/debugging.
2025-10-31 16:32:30 +01:00
Bennet Fenner
0735aca015 Update UNSUPPORTED_COMMANDS (#132)
The SDK seems to now filter out slash commands that result in
errors/crashes.

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-10-30 17:17:47 +01:00
6 changed files with 69 additions and 42 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
dist
.DS_Store

View File

@@ -1,5 +1,9 @@
# Changelog
## 0.9.0
- Support slash commands coming from MCP servers (Prompts)
## 0.8.0
- Revert changes to filename for cli entrypoint

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@zed-industries/claude-code-acp",
"version": "0.8.0",
"version": "0.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@zed-industries/claude-code-acp",
"version": "0.8.0",
"version": "0.9.0",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.5.1",

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.8.0",
"version": "0.9.0",
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
"main": "dist/lib.js",
"bin": {

View File

@@ -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({

View File

@@ -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",