Add WebSocket transport to the code-mode host (#35078)

## What changed

- Add a `--listen` option that accepts `stdio`, `stdio://`, or a
  `ws://IP:PORT` endpoint, while retaining stdio as the default.
- Serve the existing length-prefixed protocol in binary WebSocket messages,
  with isolated connections, shared host limits, and a `/readyz` endpoint.
- Reject browser-origin handshakes and contain malformed frames to the affected
  connection.

## Testing

- Cover listen URL parsing and complete-frame encoding and decoding.
- Exercise readiness, cell execution, tool callbacks, large frames, concurrent
  connections, malformed frames, and origin rejection through the WebSocket
  listener.

GitOrigin-RevId: 01c8be4c6256b8ce4a3a0002440dcb3294e5f887
This commit is contained in:
Channing Conger
2026-07-24 02:40:11 +00:00
committed by copyberry
parent f47f28cd0d
commit 0dfa778dae
11 changed files with 946 additions and 24 deletions
@@ -3,10 +3,43 @@ use serde_json::json;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use super::EncodedFrame;
use super::FramedReader;
use super::FramedWriter;
use super::MAX_FRAME_BYTES;
#[test]
fn complete_frame_round_trips_without_a_byte_stream() {
let value = json!({"type": "session/open", "sessionId": "session-1"});
let bytes = EncodedFrame::encode(&value)
.expect("encode frame")
.into_framed_bytes();
assert_eq!(
EncodedFrame::decode_framed::<serde_json::Value>(&bytes).expect("decode frame"),
value
);
}
#[test]
fn complete_frame_rejects_truncated_and_trailing_payloads() {
let value = json!({"value": 1});
let bytes = EncodedFrame::encode(&value)
.expect("encode frame")
.into_framed_bytes();
let truncated = &bytes[..bytes.len() - 1];
let truncated_error = EncodedFrame::decode_framed::<serde_json::Value>(truncated)
.expect_err("truncated frame should fail");
assert_eq!(truncated_error.kind(), std::io::ErrorKind::InvalidData);
let mut trailing = bytes;
trailing.push(0);
let trailing_error = EncodedFrame::decode_framed::<serde_json::Value>(&trailing)
.expect_err("frame with trailing bytes should fail");
assert_eq!(trailing_error.kind(), std::io::ErrorKind::InvalidData);
}
#[tokio::test]
async fn frame_wire_format_is_little_endian_length_prefixed_json() {
let (writer, mut reader) = tokio::io::duplex(/*max_buf_size*/ 128);