In the previous chapter, Assistant Message Normalization, we acted as a "Newspaper Editor," ensuring the AI's stories (Assistant Messages) were complete and readable.
Now, we turn our attention to the System itself.
Imagine you are running a restaurant.
If you serve the raw vegetables (dirt and all) to the customer, they will leave (or the App will crash).
Local Command Output Sanitization is your Food Processor. It takes the raw, dirty output from terminal commands, washes off the "dirt" (computer color codes), peels off the "skin" (internal XML tags), and chops it into a format the customer recognizes.
/cost Command
In our system, a user can type /cost to see how much money the AI has spent.
<green>$0.05</green>, it might literally display those weird characters, or worse, treat it as a broken file.We need to sanitize this text before sending it over the wire.
The logic for this lives in src/mappers.ts. The main function is localCommandOutputToSDKAssistantMessage.
This is what the system generates internally. To a human eye, it looks like mess.
// A raw string with invisible escape codes and XML tags
const rawInput = `
<local-command-stdout>
\u001b[32mTotal Cost: $0.15\u001b[39m
</local-command-stdout>
`;
The \u001b[32m is code for "Start Green Color" and \u001b[39m is "End Color".
We pass this raw mess into our sanitizer function.
import { localCommandOutputToSDKAssistantMessage } from './mappers';
const cleanMessage = localCommandOutputToSDKAssistantMessage(
rawInput,
'uuid-123'
);
The function returns a clean, standard object.
{
"type": "assistant",
"message": {
"content": "Total Cost: $0.15"
}
}
The dirt (ANSI) and skin (XML) are gone. The text is plain and readable.
How does the processor actually work? It follows a three-step cleaning cycle.
Let's look at src/mappers.ts to see the cleaning process.
First, inside the main mapping loop, we check if the message is the specific type we need to clean.
// src/mappers.ts inside toSDKMessages
// We check if the message contains stdout (output) or stderr (errors) tags
if (
message.subtype === 'local_command' &&
(message.content.includes(`<${LOCAL_COMMAND_STDOUT_TAG}>`) ||
message.content.includes(`<${LOCAL_COMMAND_STDERR_TAG}>`))
) {
// If yes, send it to the food processor!
return [localCommandOutputToSDKAssistantMessage(message.content, message.uuid)]
}
We only run the sanitizer on messages that actually contain command output.
Inside the function, we use regex (pattern matching) and a helper library to clean the string.
// src/mappers.ts
export function localCommandOutputToSDKAssistantMessage(
rawContent: string,
uuid: UUID,
) {
// 1. stripAnsi removes the color codes
// 2. .replace removes the <local-command-stdout> wrapper tags
const cleanContent = stripAnsi(rawContent)
.replace(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/, '$1')
.replace(/<local-command-stderr>([\s\S]*?)<\/local-command-stderr>/, '$1')
.trim()
// ... continued below
stripAnsi is like the water jet washing the dirt. .replace is the peeler removing the skin.
This is a critical trick. Mobile apps know how to display "User" messages and "Assistant" messages. They often don't know how to display "System Command" messages.
To prevent the app from crashing or showing an "Unknown Message Type" error, we lie slightly: We tell the App that the Assistant said this text.
// Create a standard Assistant message object
const synthetic = createAssistantMessage({ content: cleanContent })
return {
type: 'assistant', // <-- The Disguise!
message: synthetic.message,
parent_tool_use_id: null,
session_id: getSessionId(),
uuid,
}
}
By changing the type to assistant, we ensure every client (Android, iOS, Web) renders a nice text bubble without needing to update their code.
Local Command Output Sanitization ensures that raw, technical system outputs are cleaned up before reaching the user.
assistant to ensure compatibility with all User Interfaces.We have now covered how to initialize the system, translate messages, normalize AI logic, and sanitize system commands. But what happens when the conversation gets too long?
In the next chapter, we will learn how the system handles memory management.
Next Chapter: Conversation Compaction Metadata
Generated by Code IQ