In the previous chapter, Low-Level Client (The Communicator), we built the telephone line that allows our application to talk to language tools. We learned how to send requests (like "Where is this definition?") and get answers.
But what happens when the tool has something to say, and we didn't ask?
Imagine you are typing code in a file. You make a typo. Suddenly, a red squiggly line appears under the word. You didn't ask the server "Is there a typo right now?" The server saw you typing, analyzed the code in the background, and decided to tell you about the error immediately.
This creates a problem for our system:
We need a buffering system. We need a Diagnostic Feedback Loop.
Think of this as a Voicemail Inbox or a Mailbox:
In the previous chapters, we used "Request/Response" (Active).
This chapter deals with "Notifications" (Passive). The server sends data, and it doesn't expect a reply. The most common notification is textDocument/publishDiagnostics.
This is a global storage area. When a server sends an error, we don't act on it immediately. We file it away in the Registry with a timestamp.
LSP servers are chatty. If you have an error on line 10, the server might send the notification for that error 5 times in a row as you move your cursor. The Mailbox is smart. It looks at the new message, checks if it's the exact same as the last one, and if so, it ignores it. This prevents the AI from being overwhelmed by repeated complaints.
This system runs automatically in the background, but here is how the data flows when we want to check for errors.
We tell the Server Manager (from Chapter 2) to attach a listener to every server.
import { registerLSPNotificationHandlers } from './passiveFeedback';
// We do this once when the app starts
registerLSPNotificationHandlers(lspManager);
console.log("Listening for incoming error reports...");
When the AI is preparing its response to the user, it peeks into the mailbox to see if any new errors occurred during the last action.
import { checkForLSPDiagnostics } from './LSPDiagnosticRegistry';
// "Open the mailbox"
const newErrors = checkForLSPDiagnostics();
if (newErrors.length > 0) {
console.log("Hey! The server found some issues:", newErrors);
} else {
console.log("No news is good news.");
}
Let's trace the journey of a "Syntax Error" from the Python server to our AI.
The implementation is split into two files:
passiveFeedback.ts: Listens to the phone line.LSPDiagnosticRegistry.ts: Stores and manages the messages.passiveFeedback.ts)
We use the onNotification method provided by the Server Instance (from Chapter 3).
// Inside registerLSPNotificationHandlers function
serverInstance.onNotification('textDocument/publishDiagnostics', (params) => {
// 1. Convert complex LSP JSON into a simple format
const diagnosticFiles = formatDiagnosticsForAttachment(params);
// 2. Put it in the registry (The Mailbox)
registerPendingLSPDiagnostic({
serverName: serverName,
files: diagnosticFiles,
});
});
Explanation: This code acts as a bridge. It takes the raw, messy data from the server, cleans it up, and hands it off to the registry.
LSPDiagnosticRegistry.ts)
We use a Map to store pending messages.
// The storage container
const pendingDiagnostics = new Map<string, PendingLSPDiagnostic>();
export function registerPendingLSPDiagnostic(data) {
// Create a unique ID for this batch of mail
const id = randomUUID();
// Save it
pendingDiagnostics.set(id, {
...data,
timestamp: Date.now(),
attachmentSent: false
});
}
LSPDiagnosticRegistry.ts)This is the smartest part of the system. When we read the mail, we check if we've already seen this error recently.
// Helper to track what we already told the user
const deliveredDiagnostics = new LRUCache({ max: 500 });
export function checkForLSPDiagnostics() {
// 1. Get all waiting messages
const allFiles = getAllPendingFiles();
// 2. Filter out duplicates (Spam Filter)
// If we told the user about "Error on Line 10" 5 seconds ago,
// and the error is still there, don't tell them again.
const uniqueFiles = deduplicateDiagnosticFiles(allFiles);
// 3. Mark these messages as "Read" so we don't fetch them again
markDiagnosticsAsSent();
return uniqueFiles;
}
Explanation: The deduplicateDiagnosticFiles function (simplified above) is critical. It compares the file name, line number, and error message. If they match a message we recently delivered, it silently drops the new one. This keeps the conversation with the AI clean and focused.
You have now completed the entire LSP System!
Let's review what we built:
Together, these five components allow an AI to write code, ask for definitions, and receive automatic error feedback just like a human developer using a modern IDE.
End of Tutorial.
Generated by Code IQ