In the previous chapter, Unified Transport Layer (The "Pipe"), we built a pipeline that moves raw data between the server and our application.
However, a raw pipe is dumb. It delivers everything: random server pings, echoes of messages we sent ourselves, and complex commands mixed with chat text. If we just printed everything coming out of the pipe directly to the screen, it would look like gibberish.
We need a Dispatcher. Think of this as the Mail Room of a large office. It opens every envelope, throws away junk mail, downloads heavy packages, and decides exactly which department should handle the request.
Imagine the server sends this raw string down the pipe:
{"type": "user", "uuid": "abc-123", "content": "Hello", "file_attachments": [{"file_uuid": "xyz"}]}
We have three problems here:
file_attachments is just an ID (xyz). The AI cannot read an ID. It needs the actual file content on the hard drive.The Goal: We need a layer that converts raw noise into clean, ready-to-use instructions for the AI.
The Dispatcher looks at the type of the message.
Different clients (iPhone app, Web Dashboard) sometimes format data differently. For example, one might say mediaType (camelCase) and another media_type (snake_case). The Dispatcher fixes these typos so the rest of the app doesn't crash.
When a user uploads a file on the web, the message only contains a "Claim Check" (a UUID). The Dispatcher pauses, runs to the server to download the actual file, saves it to a temp folder, and then hands the message to the AI saying, "Here is the message, and I put the file at /tmp/file.txt."
Let's say a user on the web dashboard uploads a CSV file and asks "Analyze this."
The Dispatcher's Job:
Analyze this @"/local/path/to/data.csv".This process happens automatically whenever data arrives from the Transport layer.
Let's look at the three files that make up the Dispatcher.
bridgeMessaging.ts)This is the main entry point. It filters noise and routes traffic.
// bridgeMessaging.ts
export function handleIngressMessage(data, recentPostedUUIDs, onInboundMessage) {
// 1. Parse Raw String to JSON
const parsed = JSON.parse(data);
// 2. Echo Detection
// If we have seen this UUID in our "Sent" box, ignore it.
if (parsed.uuid && recentPostedUUIDs.has(parsed.uuid)) {
return; // Drop it!
}
// 3. Route based on type
if (parsed.type === 'user') {
// Send to the app (which will handle attachments next)
onInboundMessage(parsed);
}
}
Explanation: This function is the gatekeeper. It ensures that we don't get into an infinite loop of replying to our own messages (recentPostedUUIDs).
inboundMessages.ts)Sometimes, image data comes in with slightly wrong formatting. We fix it here.
// inboundMessages.ts
export function normalizeImageBlocks(blocks) {
return blocks.map(block => {
// Check if it's a base64 image
if (block.type === 'image' && block.source.type === 'base64') {
// Fix "mediaType" (wrong) to "media_type" (standard)
const fixedMediaType = block.source.mediaType || block.source.media_type;
return { ...block, source: { ...block.source, media_type: fixedMediaType }};
}
return block;
});
}
Explanation: This is a "sanitizer." By fixing the data here, the complex logic inside the AI "Brain" doesn't need to worry about inconsistent data formats.
inboundAttachments.ts)This is the heavy lifter. It converts "Claim Checks" (IDs) into actual files.
// inboundAttachments.ts
export async function resolveInboundAttachments(attachments) {
// Loop through all file IDs attached to the message
const paths = await Promise.all(attachments.map(async (att) => {
// 1. Download from API
const data = await downloadFile(att.file_uuid);
// 2. Save to local disk
const localPath = `/tmp/${att.file_name}`;
await writeFile(localPath, data);
return localPath;
}));
// Return a string of references: @"/tmp/file1.txt"
return paths.map(p => `@"${p}"`).join(' ');
}
Explanation: This function makes the bridge feel magical. You upload a file on a website, and milliseconds later, that file exists on your terminal's hard drive, ready for the AI to read.
Not all messages are chat. Sometimes the server sends a Control Request.
For example, if you click "Stop" on the web dashboard, the server sends an interrupt signal. The Dispatcher handles this separately in bridgeMessaging.ts.
// bridgeMessaging.ts
export function handleServerControlRequest(request, handlers) {
switch (request.subtype) {
case 'interrupt':
// Stop the local process
handlers.onInterrupt();
break;
case 'set_model':
// Change the AI model
handlers.onSetModel(request.model);
break;
}
}
Explanation: The Dispatcher recognizes that this isn't a message for the userβit's a command for the application itself. It routes these directly to the system handlers.
The Dispatcher is the intelligent layer that sits on top of the dumb pipe.
At this point, we have a fully functional system! We can connect, create sessions, send data, and handle complex messages.
But there is one major question left: Is this safe? How do we prevent unauthorized people from connecting to our session and running commands on our computer?
Next Chapter: Authentication & Security (The "Keycard")
Generated by Code IQ