Render read files in code blocks

This commit is contained in:
Conrad Irwin
2025-08-28 22:32:43 -06:00
parent c4674e901a
commit f18bfeaf8d
3 changed files with 79 additions and 14 deletions

View File

@@ -6,6 +6,12 @@ import { z } from "zod";
import { Server } from "node:http";
import { ClaudeAcpAgent } from "./acp-agent.js";
export const SYSTEM_REMINDER = `
<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>`;
export function createMcpServer(
agent: ClaudeAcpAgent,
sessionId: string,
@@ -70,13 +76,7 @@ In sessions with mcp__acp__read always use it instead of Read as it contains the
content: [
{
type: "text",
text:
content.content +
`
<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>`,
text: content.content + SYSTEM_REMINDER,
},
],
};

View File

@@ -13,7 +13,11 @@ import {
WriteTextFileResponse,
} from "@zed-industries/agent-client-protocol";
import { nodeToWebWritable, nodeToWebReadable } from "../utils.js";
import { toolInfoFromToolUse, toolUpdateFromToolResult } from "../tools.js";
import {
markdownEscape,
toolInfoFromToolUse,
toolUpdateFromToolResult,
} from "../tools.js";
import { toAcpNotifications } from "../acp-agent.js";
import { SDKAssistantMessage } from "@anthropic-ai/claude-code";
@@ -776,3 +780,17 @@ describe("tool conversions", () => {
expect(update).toEqual({});
});
});
describe("escape markdown", () => {
it("should escape markdown characters", () => {
let text = "Hello *world*!";
let escaped = markdownEscape(text);
expect(escaped).toEqual("```\nHello *world*!\n```");
text = "for example:\n```markdown\nHello *world*!\n```\n";
escaped = markdownEscape(text);
expect(escaped).toEqual(
"````\nfor example:\n```markdown\nHello *world*!\n```\n````",
);
});
});

View File

@@ -6,7 +6,8 @@ import {
ToolCallLocation,
ToolKind,
} from "@zed-industries/agent-client-protocol";
import { replaceAndCalculateLocation } from "./mcp-server.js";
import { replaceAndCalculateLocation, SYSTEM_REMINDER } from "./mcp-server.js";
import { tool } from "@anthropic-ai/claude-code";
interface ToolInfo {
title: string;
@@ -489,6 +490,43 @@ export function toolUpdateFromToolResult(
case "Write":
return {};
case "Read":
case "mcp__acp__read":
if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
return {
content: toolResult.content.map((content: any) => ({
type: "content",
content:
content.type == "text"
? {
type: "text",
text: markdownEscape(
content.text.replace(SYSTEM_REMINDER, ""),
),
}
: content,
})),
};
} else if (
typeof toolResult.content === "string" &&
toolResult.content.length > 0
) {
return {
content: [
{
type: "content",
content: {
type: "text",
text: markdownEscape(
toolResult.content.replace(SYSTEM_REMINDER, ""),
),
},
},
],
};
}
return {};
case "Task":
case "NotebookEdit":
case "NotebookRead":
@@ -502,12 +540,8 @@ export function toolUpdateFromToolResult(
case "Grep":
case "WebFetch":
case "WebSearch":
case "Read":
case "mcp__acp__read":
case "Other":
default: {
// This happens for the mcp__acp__read tool,
// but may also for others...
if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
return {
content: toolResult.content.map((content: any) => ({
@@ -523,7 +557,10 @@ export function toolUpdateFromToolResult(
content: [
{
type: "content",
content: { type: "text", text: toolResult.content },
content: {
type: "text",
text: toolResult.content,
},
},
],
};
@@ -546,3 +583,13 @@ export function planEntries(input: { todos: ClaudePlanEntry[] }): PlanEntry[] {
priority: "medium",
}));
}
export function markdownEscape(text: string): string {
let escape = "```";
for (let [m] of text.matchAll(/^```+/gm)) {
while (m.length >= escape.length) {
escape += "`";
}
}
return escape + "\n" + text + (text.endsWith("\n") ? "" : "\n") + escape;
}