Escape dyad tags inside thinking blocks (#229)

This commit is contained in:
Will Chen
2025-05-22 16:06:28 -07:00
committed by GitHub
parent 5e86f4b54b
commit f4c7d614bd
2 changed files with 52 additions and 4 deletions

View File

@@ -215,6 +215,7 @@ export function registerChatStreamHandlers() {
abortController, abortController,
updatedChat, updatedChat,
); );
fullResponse = cleanThinkingByEscapingDyadTags(fullResponse);
} else { } else {
// Normal AI processing for non-test prompts // Normal AI processing for non-test prompts
const settings = readSettings(); const settings = readSettings();
@@ -348,7 +349,10 @@ This conversation includes one or more image attachments. When the user uploads
...codebasePrefix, ...codebasePrefix,
...limitedMessageHistory.map((msg) => ({ ...limitedMessageHistory.map((msg) => ({
role: msg.role as "user" | "assistant" | "system", role: msg.role as "user" | "assistant" | "system",
content: msg.content, // Why remove thinking tags?
// Thinking tags are generally not critical for the context
// and eats up extra tokens.
content: removeThinkingTags(msg.content),
})), })),
]; ];
@@ -411,6 +415,7 @@ This conversation includes one or more image attachments. When the user uploads
try { try {
for await (const textPart of textStream) { for await (const textPart of textStream) {
fullResponse += textPart; fullResponse += textPart;
fullResponse = cleanThinkingByEscapingDyadTags(fullResponse);
if ( if (
fullResponse.includes("$$SUPABASE_CLIENT_CODE$$") && fullResponse.includes("$$SUPABASE_CLIENT_CODE$$") &&
updatedChat.app?.supabaseProjectId updatedChat.app?.supabaseProjectId
@@ -707,3 +712,27 @@ async function prepareMessageWithAttachments(
content: contentParts, content: contentParts,
}; };
} }
function cleanThinkingByEscapingDyadTags(text: string): string {
// Extract content inside <think> </think> tags
const thinkRegex = /<think>([\s\S]*?)<\/think>/g;
return text.replace(thinkRegex, (match, content) => {
// We are replacing the opening tag with a look-alike character
// to avoid issues where thinking content includes dyad tags
// and are mishandled by:
// 1. FE markdown parser
// 2. Main process response processor
const processedContent = content
.replace(/<dyad/g, "dyad")
.replace(/<\/dyad/g, "/dyad");
// Return the modified think tag with processed content
return `<think>${processedContent}</think>`;
});
}
function removeThinkingTags(text: string): string {
const thinkRegex = /<think>([\s\S]*?)<\/think>/g;
return text.replace(thinkRegex, "").trim();
}

View File

@@ -32,9 +32,28 @@ function createStreamChunk(
return `data: ${JSON.stringify(chunk)}\n\n${isLast ? "data: [DONE]\n\n" : ""}`; return `data: ${JSON.stringify(chunk)}\n\n${isLast ? "data: [DONE]\n\n" : ""}`;
} }
const CANNED_MESSAGE = `
<think>
\`<dyad-write>\`:
I'll think about the problem and write a bug report.
<dyad-write>
<dyad-write path="file1.txt">
Fake dyad write
</dyad-write>
</think>
<dyad-write path="file1.txt">
A file (2)
</dyad-write>
More
EOM`;
// Handle POST requests to /v1/chat/completions // Handle POST requests to /v1/chat/completions
app.post("/v1/chat/completions", (req, res) => { app.post("/v1/chat/completions", (req, res) => {
const { stream = false, messages = [] } = req.body; const { stream = false, messages = [] } = req.body;
console.log("* Received messages", messages);
// Check if the last message contains "[429]" to simulate rate limiting // Check if the last message contains "[429]" to simulate rate limiting
const lastMessage = messages[messages.length - 1]; const lastMessage = messages[messages.length - 1];
@@ -61,7 +80,7 @@ app.post("/v1/chat/completions", (req, res) => {
index: 0, index: 0,
message: { message: {
role: "assistant", role: "assistant",
content: "hello world", content: CANNED_MESSAGE,
}, },
finish_reason: "stop", finish_reason: "stop",
}, },
@@ -75,7 +94,7 @@ app.post("/v1/chat/completions", (req, res) => {
res.setHeader("Connection", "keep-alive"); res.setHeader("Connection", "keep-alive");
// Split the "hello world" message into characters to simulate streaming // Split the "hello world" message into characters to simulate streaming
const message = "hello world"; const message = CANNED_MESSAGE;
const messageChars = message.split(""); const messageChars = message.split("");
// Stream each character with a delay // Stream each character with a delay
@@ -94,7 +113,7 @@ app.post("/v1/chat/completions", (req, res) => {
clearInterval(interval); clearInterval(interval);
res.end(); res.end();
} }
}, 100); }, 10);
}); });
// Start the server // Start the server