In the previous chapter, Session Lifecycle & Compatibility (The "Container"), we learned how to create a session "folder" to hold our work.
But a folder is useless if we can't put anything inside it. We need a way to send commands and receive output. We need a connection.
However, connecting is complicated. Sometimes we use old protocols (WebSockets), and sometimes we use new ones (Server-Sent Events). We don't want our main application to worry about how the data moves; we just want it to move.
This is the job of the Unified Transport Layer, or "The Pipe."
Imagine you travel to different countries. In one country, the wall socket has two round holes. In another, it has three flat pins.
You don't want to buy a new phone for every country. You want a Universal Adapter. You plug your phone into the adapter, and the adapter handles the messy reality of the wall socket.
In the Bridge project:
This layer defines a strict set of rules. No matter what technology we use underneath, the Transport must provide simple buttons like:
connect(): Turn it on.write(message): Send data.setOnData(callback): Listen for data.This acts like a walkie-talkie. It uses a WebSocket to listen for messages and uses standard HTTP POST requests to talk back. It's reliable but older technology.
This is the modern "streaming" approach. It uses Server-Sent Events (SSE).
The Unified Transport Layer looks at the configuration and automatically picks the right one.
If you are writing high-level code, you don't need to know about v1 or v2. You just use the generic object.
// 1. Create the transport (The factory does the hard work)
const pipe = await createV2ReplTransport({ ...config });
// 2. Setup: What do we do when data arrives?
pipe.setOnData((incomingData) => {
console.log("Server said:", incomingData);
});
// 3. Connect the wires
pipe.connect();
// 4. Send a message
// We don't care if this uses POST, WebSockets, or Carrier Pigeons.
await pipe.write({ role: "user", content: "Hello World" });
How does the system decide which pipe to build and how to route the traffic?
Let's look at replBridgeTransport.ts to see how this adapter is built.
First, we define what a Transport is. This is a TypeScript type definition. It guarantees that any transport we build will look exactly the same to the rest of the app.
// replBridgeTransport.ts
export type ReplBridgeTransport = {
// Send a single message
write(message: StdoutMessage): Promise<void>
// Listen for incoming data strings
setOnData(callback: (data: string) => void): void
// Start the connection
connect(): void
// Clean up and hang up
close(): void
}
Explanation: This is the contract. If you build a v3 transport in the future, it just needs to follow these four rules, and the rest of the app won't need to change.
This function creates the modern version of the pipe. It combines SSE (for reading) and CCR (for writing).
// replBridgeTransport.ts
export async function createV2ReplTransport(opts): Promise<ReplBridgeTransport> {
const { sessionUrl, ingressToken, sessionId } = opts;
// 1. Setup the Reader (Server-Sent Events)
const sse = new SSETransport(sseUrl, headers, sessionId);
// 2. Setup the Writer (Command Control Region)
const ccr = new CCRClient(sse, sessionUrl, { ... });
// 3. Return the Unified Interface
return {
write: (msg) => ccr.writeEvent(msg), // Delegate write to CCR
setOnData: (cb) => sse.setOnData(cb), // Delegate read to SSE
connect: () => {
sse.connect(); // Start listening
ccr.initialize(); // Start writing
},
close: () => { ccr.close(); sse.close(); }
};
}
Explanation:
sse object to handle incoming data.ccr object to handle outgoing data.write to ccr and onData to sse.The "Pipe" isn't just dumb wires; it also tells the server if we are alive.
// inside createV2ReplTransport...
// If the server sends an event, acknowledge we got it
sse.setOnEvent(event => {
// Tell server: "I received message ID 123"
ccr.reportDelivery(event.event_id, 'received');
// Tell server: "I processed message ID 123"
ccr.reportDelivery(event.event_id, 'processed');
});
Explanation: This is invisible to the user. The transport layer automatically whispers to the server, "I got the message," ensuring the server doesn't try to resend it. This prevents duplicate messages and keeps the connection healthy.
The Unified Transport Layer is the great simplifier.
write, connect, onData).Now that we have a Brain (Core), a Container (Session), and a Pipe (Transport), raw text is flowing into our application. But raw text isn't enoughβwe need to know if that text is a chat message, a code execution result, or an error.
We need a traffic controller.
Next Chapter: Message Routing & Data Flow (The "Dispatcher")
Generated by Code IQ