From d15bf1deb77e0b43dd73ee7dbf7fd6487e6c5837 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 10 Nov 2026 04:19:51 +0000 Subject: [PATCH] Fragment large Responses WebSocket messages into bounded frames (#60522) ## What changed Send Responses text messages larger than 1 MiB as a single WebSocket message split into frames of at most 1 MiB, awaiting each frame send to apply backpressure. Keep smaller text messages and non-text messages on the existing send path. Update the `tokio-tungstenite` and `tungstenite` fork revisions and associated Cargo and Bazel lockfiles. ## Testing Add a unit test covering the 1 MiB boundary, continuation opcodes, the final-frame flag, and preservation of payload bytes when a multibyte UTF-8 character crosses a frame boundary. GitOrigin-RevId: 372ee31b77e185b963d3bdca11847e50e9018421 --- MODULE.bazel.lock | 8 +-- codex-rs/Cargo.lock | 4 +- codex-rs/Cargo.toml | 6 +- .../src/endpoint/responses_websocket.rs | 68 ++++++++++++++++++- 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index e829f764e9..2f89781ba1 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1382,8 +1382,8 @@ "git+https://github.com/modelcontextprotocol/rust-sdk?rev=98a54789b80110ad210495c4ad845c89e1980471#98a54789b80110ad210495c4ad845c89e1980471_rmcp-macros": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"darling\",\"optional\":false,\"req\":\"0.24\"},{\"name\":\"proc-macro2\",\"req\":\"1\"},{\"name\":\"quote\",\"req\":\"1\"},{\"name\":\"serde_json\",\"req\":\"1.0\"},{\"default_features\":true,\"features\":[\"full\"],\"name\":\"syn\",\"optional\":false,\"req\":\"3\"}],\"features\":{\"local\":[]},\"strip_prefix\":\"./crates/rmcp-macros\"}", "git+https://github.com/openai-oss-forks/crossterm?rev=ed1cdab335221515706178d68495bba2aed1924f#ed1cdab335221515706178d68495bba2aed1924f_crossterm": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"base64\",\"optional\":true,\"req\":\"0.23\"},{\"default_features\":true,\"features\":[],\"name\":\"bitflags\",\"optional\":false,\"req\":\"2.9\"},{\"default_features\":true,\"features\":[\"is_variant\"],\"name\":\"derive_more\",\"optional\":true,\"req\":\"2.0.0\"},{\"default_features\":true,\"features\":[],\"name\":\"document-features\",\"optional\":true,\"req\":\"0.2.11\"},{\"default_features\":false,\"features\":[],\"name\":\"futures-core\",\"optional\":true,\"req\":\"0.3.31\"},{\"name\":\"parking_lot\",\"req\":\"0.12.1\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"1.0.103\"},{\"default_features\":true,\"features\":[],\"name\":\"filedescriptor\",\"optional\":true,\"req\":\"0.8\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"features\":[],\"name\":\"libc\",\"optional\":true,\"req\":\"0.2.168\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[\"os-poll\"],\"name\":\"mio\",\"optional\":true,\"req\":\"1.0.1\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"features\":[\"std\",\"stdio\",\"termios\"],\"name\":\"rustix\",\"optional\":false,\"req\":\"1\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[],\"name\":\"signal-hook\",\"optional\":true,\"req\":\"0.3.17\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[\"support-v1_0\"],\"name\":\"signal-hook-mio\",\"optional\":true,\"req\":\"0.2.4\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[],\"name\":\"crossterm_winapi\",\"optional\":true,\"req\":\"0.9.1\",\"target\":\"cfg(windows)\"},{\"default_features\":true,\"features\":[\"winuser\",\"winerror\"],\"name\":\"winapi\",\"optional\":true,\"req\":\"0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{\"bracketed-paste\":[],\"default\":[\"bracketed-paste\",\"derive-more\",\"events\",\"windows\"],\"derive-more\":[\"dep:derive_more\"],\"document-features\":[\"dep:document-features\"],\"event-stream\":[\"dep:futures-core\",\"events\"],\"events\":[\"dep:mio\",\"dep:signal-hook\",\"dep:signal-hook-mio\"],\"osc52\":[\"dep:base64\"],\"serde\":[\"dep:serde\",\"bitflags/serde\"],\"use-dev-tty\":[\"filedescriptor\",\"rustix/process\"],\"windows\":[\"dep:winapi\",\"dep:crossterm_winapi\"]},\"strip_prefix\":\"\"}", "git+https://github.com/openai-oss-forks/rusty_v8.git?rev=90a65f887a0c0f048f3784afbcb320b373c8a2e0#90a65f887a0c0f048f3784afbcb320b373c8a2e0_v8": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"2.5\"},{\"name\":\"paste\",\"req\":\"1.0\"},{\"default_features\":true,\"features\":[\"zoneinfo64\"],\"name\":\"temporal_capi\",\"optional\":false,\"req\":\"0.2.3\"},{\"kind\":\"build\",\"name\":\"bindgen\",\"req\":\"0.72\"},{\"kind\":\"build\",\"name\":\"fslock\",\"req\":\"0.2\"},{\"kind\":\"build\",\"name\":\"gzip-header\",\"req\":\"1.0.0\"},{\"kind\":\"build\",\"name\":\"home\",\"req\":\"0\"},{\"kind\":\"build\",\"name\":\"miniz_oxide\",\"req\":\"0.8.8\"},{\"kind\":\"build\",\"name\":\"which\",\"req\":\"6\"}],\"features\":{\"default\":[\"use_custom_libcxx\"],\"simdutf\":[],\"use_custom_libcxx\":[],\"v8_enable_pointer_compression\":[],\"v8_enable_sandbox\":[\"v8_enable_pointer_compression\"],\"v8_enable_v8_checks\":[]},\"strip_prefix\":\"\"}", - "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=02cb49fa827411e9279e3688d19ab9e82661b0e8#02cb49fa827411e9279e3688d19ab9e82661b0e8_tokio-tungstenite": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"optional\":false,\"req\":\"0.3.28\"},{\"name\":\"log\",\"req\":\"0.4.17\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.11\"},{\"default_features\":false,\"features\":[],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":false,\"req\":\"1.0.0\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"0.3.1\"},{\"default_features\":false,\"features\":[],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"0.26.0\"},{\"default_features\":false,\"features\":[],\"name\":\"tungstenite\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26.0\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\",\"tokio-rustls\",\"stream\",\"tungstenite/__rustls-tls\",\"handshake\"],\"connect\":[\"stream\",\"tokio/net\",\"tokio/time\",\"handshake\"],\"default\":[\"connect\",\"handshake\"],\"handshake\":[\"tungstenite/handshake\"],\"native-tls\":[\"native-tls-crate\",\"tokio-native-tls\",\"stream\",\"tungstenite/native-tls\",\"handshake\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\",\"tungstenite/native-tls-vendored\"],\"proxy\":[\"tungstenite/proxy\",\"tokio/net\",\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"stream\":[],\"url\":[\"tungstenite/url\"]},\"strip_prefix\":\"\"}", - "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=db4be399bc1b8b5de2edb46c7187834edabf7e30#db4be399bc1b8b5de2edb46c7187834edabf7e30_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"1.9.0\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true,\"req\":\"2\"},{\"default_features\":false,\"features\":[\"zlib-rs\"],\"name\":\"flate2\",\"optional\":true,\"req\":\"1.1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true,\"req\":\"0.4.0\"},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true,\"req\":\"1.3.4\"},{\"name\":\"log\",\"req\":\"0.4.8\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.3\"},{\"name\":\"rand\",\"req\":\"0.9.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true,\"req\":\"0.10\"},{\"name\":\"thiserror\",\"req\":\"2.0.7\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true,\"req\":\"2.1.0\"},{\"name\":\"utf-8\",\"req\":\"0.7.5\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"deflate\":[\"headers\",\"flate2\"],\"handshake\":[\"data-encoding\",\"headers\",\"httparse\",\"sha1\"],\"headers\":[\"http\",\"dep:headers\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}", + "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=7447c427513cfe685f236e1ac0513c4ea3dd9c0b#7447c427513cfe685f236e1ac0513c4ea3dd9c0b_tokio-tungstenite": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"optional\":false,\"req\":\"0.3.28\"},{\"name\":\"log\",\"req\":\"0.4.17\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.11\"},{\"default_features\":false,\"features\":[],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":false,\"req\":\"1.0.0\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"0.3.1\"},{\"default_features\":false,\"features\":[],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"0.26.0\"},{\"default_features\":false,\"features\":[],\"name\":\"tungstenite\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26.0\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\",\"tokio-rustls\",\"stream\",\"tungstenite/__rustls-tls\",\"handshake\"],\"connect\":[\"stream\",\"tokio/net\",\"tokio/time\",\"handshake\"],\"default\":[\"connect\",\"handshake\"],\"handshake\":[\"tungstenite/handshake\"],\"native-tls\":[\"native-tls-crate\",\"tokio-native-tls\",\"stream\",\"tungstenite/native-tls\",\"handshake\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\",\"tungstenite/native-tls-vendored\"],\"proxy\":[\"tungstenite/proxy\",\"tokio/net\",\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"stream\":[],\"url\":[\"tungstenite/url\"]},\"strip_prefix\":\"\"}", + "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=bc589d684755a8c02da0fc7e58d0861cbfc306f1#bc589d684755a8c02da0fc7e58d0861cbfc306f1_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"1.9.0\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true,\"req\":\"2\"},{\"default_features\":false,\"features\":[\"zlib-rs\"],\"name\":\"flate2\",\"optional\":true,\"req\":\"1.1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true,\"req\":\"0.4.0\"},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true,\"req\":\"1.3.4\"},{\"name\":\"log\",\"req\":\"0.4.8\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.3\"},{\"name\":\"rand\",\"req\":\"0.9.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true,\"req\":\"0.10\"},{\"name\":\"thiserror\",\"req\":\"2.0.7\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true,\"req\":\"2.1.0\"},{\"name\":\"utf-8\",\"req\":\"0.7.5\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"deflate\":[\"headers\",\"flate2\"],\"handshake\":[\"data-encoding\",\"headers\",\"httparse\",\"sha1\"],\"headers\":[\"http\",\"dep:headers\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}", "git+https://github.com/rust-lang/rust-clippy?rev=21baba9c10c5d179915cae82e6247bc2337872fb#21baba9c10c5d179915cae82e6247bc2337872fb_clippy_utils": "{\"dependencies\":[{\"default_features\":false,\"features\":[],\"name\":\"arrayvec\",\"optional\":false,\"req\":\"0.7\"},{\"name\":\"itertools\",\"req\":\"0.15\"},{\"name\":\"rustc_apfloat\",\"req\":\"0.2.0\"}],\"features\":{},\"strip_prefix\":\"clippy_utils\"}", "git+https://github.com/transact-rs/sqlx?rev=10462fa4414c6c7f1ed1d39bb31678e3b726878c#10462fa4414c6c7f1ed1d39bb31678e3b726878c_sqlx-core": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"async-fs\",\"optional\":true,\"req\":\"2.1\"},{\"default_features\":false,\"features\":[\"async-io\"],\"name\":\"async-global-executor\",\"optional\":true,\"req\":\"3.1\"},{\"default_features\":true,\"features\":[],\"name\":\"async-io\",\"optional\":true,\"req\":\"2.4.1\"},{\"default_features\":true,\"features\":[],\"name\":\"async-std\",\"optional\":true,\"req\":\"1.13\"},{\"default_features\":true,\"features\":[],\"name\":\"async-task\",\"optional\":true,\"req\":\"4.7.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"base64\",\"optional\":false,\"req\":\"0.22.1\"},{\"name\":\"bigdecimal\",\"optional\":true,\"req\":\"0.4.0\"},{\"name\":\"bit-vec\",\"optional\":true,\"req\":\"0.8\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"optional\":true,\"req\":\"1.0.1\"},{\"name\":\"bytes\",\"req\":\"1.2.0\"},{\"name\":\"cfg-if\",\"req\":\"1.0.0\"},{\"default_features\":false,\"features\":[\"std\",\"clock\"],\"name\":\"chrono\",\"optional\":true,\"req\":\"0.4.34\"},{\"default_features\":true,\"features\":[],\"name\":\"crc\",\"optional\":true,\"req\":\"3\"},{\"name\":\"crossbeam-queue\",\"req\":\"0.3.2\"},{\"name\":\"either\",\"req\":\"1.6.1\"},{\"name\":\"event-listener\",\"req\":\"5.2.0\"},{\"default_features\":false,\"features\":[],\"name\":\"futures-core\",\"optional\":false,\"req\":\"0.3.32\"},{\"name\":\"futures-intrusive\",\"req\":\"0.5.0\"},{\"name\":\"futures-io\",\"req\":\"0.3.32\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\",\"io\"],\"name\":\"futures-util\",\"optional\":false,\"req\":\"0.3.32\"},{\"name\":\"hashbrown\",\"req\":\"0.16.0\"},{\"name\":\"hashlink\",\"req\":\"0.11.0\"},{\"name\":\"indexmap\",\"req\":\"2.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"2.3.0\"},{\"name\":\"ipnetwork\",\"optional\":true,\"req\":\"0.21.1\"},{\"default_features\":false,\"features\":[],\"name\":\"log\",\"optional\":false,\"req\":\"0.4.18\"},{\"name\":\"mac_address\",\"optional\":true,\"req\":\"1.1.5\"},{\"default_features\":false,\"features\":[],\"name\":\"memchr\",\"optional\":false,\"req\":\"2.5.0\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls\",\"optional\":true,\"req\":\"0.2.10\"},{\"name\":\"percent-encoding\",\"req\":\"2.3.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rust_decimal\",\"optional\":true,\"req\":\"1.36.0\"},{\"default_features\":false,\"features\":[\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.24\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"1.0.219\"},{\"default_features\":true,\"features\":[\"raw_value\"],\"name\":\"serde_json\",\"optional\":true,\"req\":\"1.0.142\"},{\"default_features\":false,\"features\":[],\"name\":\"sha2\",\"optional\":true,\"req\":\"0.11.0\"},{\"name\":\"smallvec\",\"req\":\"1.13.1\"},{\"default_features\":false,\"features\":[],\"name\":\"smol\",\"optional\":true,\"req\":\"2.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"thiserror\",\"optional\":false,\"req\":\"2.0.18\"},{\"default_features\":true,\"features\":[\"formatting\",\"parsing\",\"macros\"],\"name\":\"time\",\"optional\":true,\"req\":\"0.3.48\"},{\"default_features\":false,\"features\":[\"time\",\"net\",\"sync\",\"fs\",\"io-util\",\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"1.25.0\"},{\"default_features\":true,\"features\":[\"fs\"],\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"0.1.8\"},{\"default_features\":true,\"features\":[],\"name\":\"toml\",\"optional\":true,\"req\":\"0.8.16\"},{\"default_features\":true,\"features\":[\"log\"],\"name\":\"tracing\",\"optional\":false,\"req\":\"0.1.37\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":false,\"req\":\"2.2.2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"1.12.1\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"1\"}],\"features\":{\"_rt-async-global-executor\":[\"async-global-executor\",\"_rt-async-io\",\"_rt-async-task\"],\"_rt-async-io\":[\"async-io\",\"async-fs\"],\"_rt-async-std\":[\"async-std\",\"_rt-async-io\"],\"_rt-async-task\":[\"async-task\"],\"_rt-smol\":[\"smol\",\"_rt-async-io\",\"_rt-async-task\"],\"_rt-tokio\":[\"tokio\",\"tokio-stream\"],\"_tls-native-tls\":[\"native-tls\"],\"_tls-none\":[],\"_tls-rustls\":[\"rustls\"],\"_tls-rustls-aws-lc-rs\":[\"_tls-rustls\",\"rustls/aws-lc-rs\",\"webpki-roots\"],\"_tls-rustls-ring-native-roots\":[\"_tls-rustls\",\"rustls/ring\",\"rustls-native-certs\"],\"_tls-rustls-ring-webpki\":[\"_tls-rustls\",\"rustls/ring\",\"webpki-roots\"],\"_unstable-doc\":[\"sqlx-toml\"],\"any\":[],\"default\":[],\"json\":[\"serde\",\"serde_json\"],\"migrate\":[\"sha2\",\"crc\"],\"offline\":[\"serde\",\"either/serde\"],\"sqlx-toml\":[\"serde\",\"toml/parse\"]},\"strip_prefix\":\"sqlx-core\"}", "git+https://github.com/transact-rs/sqlx?rev=10462fa4414c6c7f1ed1d39bb31678e3b726878c#10462fa4414c6c7f1ed1d39bb31678e3b726878c_sqlx-sqlite": "{\"dependencies\":[{\"name\":\"atoi\",\"req\":\"2.0\"},{\"default_features\":false,\"features\":[\"std\",\"clock\"],\"name\":\"chrono\",\"optional\":true,\"req\":\"0.4.34\"},{\"default_features\":false,\"features\":[\"async\"],\"name\":\"flume\",\"optional\":false,\"req\":\"0.12.0\"},{\"name\":\"form_urlencoded\",\"req\":\"1.2.2\"},{\"default_features\":false,\"features\":[\"sink\",\"alloc\",\"std\"],\"name\":\"futures-channel\",\"optional\":false,\"req\":\"0.3.32\"},{\"default_features\":false,\"features\":[],\"name\":\"futures-core\",\"optional\":false,\"req\":\"0.3.32\"},{\"default_features\":true,\"features\":[],\"name\":\"futures-executor\",\"optional\":false,\"req\":\"0.3.32\"},{\"name\":\"futures-intrusive\",\"req\":\"0.5.0\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\"],\"name\":\"futures-util\",\"optional\":false,\"req\":\"0.3.32\"},{\"default_features\":false,\"features\":[\"pkg-config\",\"vcpkg\"],\"name\":\"libsqlite3-sys\",\"optional\":false,\"req\":\">=0.30.1, <0.40.0\"},{\"name\":\"log\",\"req\":\"0.4.18\"},{\"name\":\"percent-encoding\",\"req\":\"2.3.0\"},{\"default_features\":true,\"features\":[],\"name\":\"regex\",\"optional\":true,\"req\":\"1.6.0\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"1.0.219\"},{\"default_features\":true,\"features\":[],\"name\":\"sqlx-core\",\"optional\":false,\"req\":\"=0.9.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"thiserror\",\"optional\":false,\"req\":\"2.0.18\"},{\"default_features\":true,\"features\":[\"formatting\",\"parsing\",\"macros\"],\"name\":\"time\",\"optional\":true,\"req\":\"0.3.48\"},{\"default_features\":true,\"features\":[\"log\"],\"name\":\"tracing\",\"optional\":false,\"req\":\"0.1.37\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":false,\"req\":\"2.2.2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"1.12.1\"}],\"features\":{\"_unstable-all-sqlite-features\":[\"deserialize\",\"load-extension\",\"preupdate-hook\",\"unlock-notify\"],\"_unstable-all-types\":[\"json\",\"chrono\",\"time\",\"uuid\"],\"_unstable-docs\":[\"bundled\",\"any\",\"_unstable-all-types\",\"_unstable-all-sqlite-features\"],\"any\":[\"sqlx-core/any\"],\"bigdecimal\":[],\"bundled\":[\"libsqlite3-sys/bundled\"],\"chrono\":[\"dep:chrono\",\"sqlx-core/chrono\"],\"deserialize\":[],\"json\":[\"sqlx-core/json\",\"serde\"],\"load-extension\":[],\"migrate\":[\"sqlx-core/migrate\"],\"offline\":[\"sqlx-core/offline\",\"serde\"],\"preupdate-hook\":[\"libsqlite3-sys/preupdate_hook\"],\"regexp\":[\"dep:regex\"],\"rust_decimal\":[],\"sqlx-toml\":[\"sqlx-core/sqlx-toml\"],\"time\":[\"dep:time\",\"sqlx-core/time\"],\"unbundled\":[\"libsqlite3-sys/buildtime_bindgen\"],\"unlock-notify\":[\"libsqlite3-sys/unlock_notify\"],\"uuid\":[\"dep:uuid\",\"sqlx-core/uuid\"]},\"strip_prefix\":\"sqlx-sqlite\"}", @@ -1395,8 +1395,8 @@ "git_archive_integrity:https://codeload.github.com/modelcontextprotocol/rust-sdk/tar.gz/98a54789b80110ad210495c4ad845c89e1980471": "sha256-u1NcSLjm/O48vcV54D6qYiUrp0n/IW7+GZ2DcSLJ/lI=", "git_archive_integrity:https://codeload.github.com/openai-oss-forks/crossterm/tar.gz/ed1cdab335221515706178d68495bba2aed1924f": "sha256-NTDNN30IByL2McZWbl7bSESri+j0sYXWvkoru6VZFTE=", "git_archive_integrity:https://codeload.github.com/openai-oss-forks/rusty_v8/tar.gz/90a65f887a0c0f048f3784afbcb320b373c8a2e0": "sha256-sLGI0fHYUavXgzeBiiihXoTxQ7CCzpZpdukgZhdoQ3M=", - "git_archive_integrity:https://codeload.github.com/openai-oss-forks/tokio-tungstenite/tar.gz/02cb49fa827411e9279e3688d19ab9e82661b0e8": "sha256-NQVRikscRSkjjX21eSPXIPUO986793dUN+K0XkB4BTI=", - "git_archive_integrity:https://codeload.github.com/openai-oss-forks/tungstenite-rs/tar.gz/db4be399bc1b8b5de2edb46c7187834edabf7e30": "sha256-M13cpAl8xKXQ5fXakPMxlfIpXKOYBMi/cBoBA6xvhFo=", + "git_archive_integrity:https://codeload.github.com/openai-oss-forks/tokio-tungstenite/tar.gz/7447c427513cfe685f236e1ac0513c4ea3dd9c0b": "sha256-RqW7OJVRvLIWfDNmNfxOcxrYW1dy5M6LrBwvXdCqfLw=", + "git_archive_integrity:https://codeload.github.com/openai-oss-forks/tungstenite-rs/tar.gz/bc589d684755a8c02da0fc7e58d0861cbfc306f1": "sha256-1wR/jnCUwIJevFAotK6wvvee02nkOj4lCzLhdV29c18=", "git_archive_integrity:https://codeload.github.com/rust-lang/rust-clippy/tar.gz/21baba9c10c5d179915cae82e6247bc2337872fb": "sha256-hoAjPoWso23OSqakAcd6ghJRUEZp1CiOnHQPZpRNFS4=", "git_archive_integrity:https://codeload.github.com/transact-rs/sqlx/tar.gz/10462fa4414c6c7f1ed1d39bb31678e3b726878c": "sha256-jFe976E+ztpfve0rVarAyR59YIlWuCD4HS7GstpgFN4=", "gix-actor_0.44.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-date\",\"req\":\"^0.18.0\"},{\"features\":[\"bstr\"],\"name\":\"gix-error\",\"req\":\"^0.5.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-date/serde\"]}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 6b9ad62907..481a421dee 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -16043,7 +16043,7 @@ dependencies = [ [[package]] name = "tokio-tungstenite" version = "0.28.0" -source = "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=02cb49fa827411e9279e3688d19ab9e82661b0e8#02cb49fa827411e9279e3688d19ab9e82661b0e8" +source = "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=7447c427513cfe685f236e1ac0513c4ea3dd9c0b#7447c427513cfe685f236e1ac0513c4ea3dd9c0b" dependencies = [ "futures-util", "log", @@ -16531,7 +16531,7 @@ dependencies = [ [[package]] name = "tungstenite" version = "0.27.0" -source = "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=db4be399bc1b8b5de2edb46c7187834edabf7e30#db4be399bc1b8b5de2edb46c7187834edabf7e30" +source = "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=bc589d684755a8c02da0fc7e58d0861cbfc306f1#bc589d684755a8c02da0fc7e58d0861cbfc306f1" dependencies = [ "bytes", "data-encoding", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 623f00cf16..272fa60a2a 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -685,12 +685,12 @@ crossterm = { git = "https://github.com/openai-oss-forks/crossterm", rev = "ed1c # the same reviewed source until a release includes transact-rs/sqlx#4502. sqlx-core = { git = "https://github.com/transact-rs/sqlx", rev = "10462fa4414c6c7f1ed1d39bb31678e3b726878c" } sqlx-sqlite = { git = "https://github.com/transact-rs/sqlx", rev = "10462fa4414c6c7f1ed1d39bb31678e3b726878c" } -tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungstenite", rev = "02cb49fa827411e9279e3688d19ab9e82661b0e8" } -tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "db4be399bc1b8b5de2edb46c7187834edabf7e30" } +tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungstenite", rev = "7447c427513cfe685f236e1ac0513c4ea3dd9c0b" } +tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "bc589d684755a8c02da0fc7e58d0861cbfc306f1" } v8 = { git = "https://github.com/openai-oss-forks/rusty_v8.git", rev = "90a65f887a0c0f048f3784afbcb320b373c8a2e0" } # Uncomment to debug local changes. # rmcp = { path = "../../rust-sdk/crates/rmcp" } [patch."ssh://git@github.com/openai-oss-forks/tungstenite-rs.git"] -tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "db4be399bc1b8b5de2edb46c7187834edabf7e30" } +tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "bc589d684755a8c02da0fc7e58d0861cbfc306f1" } diff --git a/codex-rs/codex-api/src/endpoint/responses_websocket.rs b/codex-rs/codex-api/src/endpoint/responses_websocket.rs index 98e53543a4..015c8a7ce8 100644 --- a/codex-rs/codex-api/src/endpoint/responses_websocket.rs +++ b/codex-rs/codex-api/src/endpoint/responses_websocket.rs @@ -18,6 +18,7 @@ use crate::safety_buffering::treatment_from_headers; use crate::sse::ResponsesStreamEvent; use crate::sse::process_responses_event; use crate::telemetry::WebsocketTelemetry; +use bytes::Bytes; use codex_client::TransportError; use codex_http_client::HttpClientFactory; use codex_http_client::RetryAfter; @@ -44,6 +45,9 @@ use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::tungstenite::client::IntoClientRequest; use tokio_tungstenite::tungstenite::handshake::client::Response; use tokio_tungstenite::tungstenite::protocol::CloseFrame; +use tokio_tungstenite::tungstenite::protocol::frame::Fragment; +use tokio_tungstenite::tungstenite::protocol::frame::Frame; +use tokio_tungstenite::tungstenite::protocol::frame::coding::Data; use tracing::Instrument; use tracing::Span; use tracing::debug; @@ -69,6 +73,36 @@ enum WsCommand { }, } +/// Send large Responses turns as one text message with backpressure between frames. +async fn send_message( + socket: &mut (impl futures::Sink + Unpin), + message: Message, +) -> Result<(), WsError> { + const FRAGMENT_SIZE: usize = 1024 * 1024; + + let Message::Text(text) = message else { + return socket.send(message).await; + }; + if text.len() <= FRAGMENT_SIZE { + return socket.send(Message::Text(text)).await; + } + + let mut data = Bytes::from(text); + let mut opcode = Data::Text; + while !data.is_empty() { + let payload = data.split_to(data.len().min(FRAGMENT_SIZE)); + let fragment = if data.is_empty() { + Fragment::Final + } else { + Fragment::More + }; + let frame = Frame::message_fragment(payload, opcode, fragment); + socket.send(Message::Frame(frame)).await?; + opcode = Data::Continue; + } + Ok(()) +} + impl WsStream { fn new(inner: WebSocketConnection) -> Self { let routing_affinity = inner.routing_affinity().cloned(); @@ -85,7 +119,7 @@ impl WsStream { }; match command { WsCommand::Send { message, tx_result } => { - let result = inner.send(message).await; + let result = send_message(&mut inner, message).await; let should_break = result.is_err(); let _ = tx_result.send(result); if should_break { @@ -1173,6 +1207,38 @@ mod tests { assert_eq!(wire_payload, expected_payload); } + #[tokio::test] + async fn large_turn_is_one_text_message_in_bounded_frames() { + const MIB: usize = 1024 * 1024; + let (tx, mut frames) = futures::channel::mpsc::unbounded(); + let mut socket = tx.sink_map_err(|_| WsError::ConnectionClosed); + + let at_limit = Message::text("a".repeat(MIB)); + send_message(&mut socket, at_limit.clone()).await.unwrap(); + assert_eq!(frames.next().await, Some(at_limit)); + + let mut text = "a".repeat(MIB - 1); + text.push('🌱'); + text.push_str(&"b".repeat(MIB)); + send_message(&mut socket, Message::text(text.clone())) + .await + .unwrap(); + drop(socket); + for (i, expected) in text.as_bytes().chunks(MIB).enumerate() { + let Some(Message::Frame(frame)) = frames.next().await else { + panic!("expected a data frame"); + }; + let opcode = if i == 0 { Data::Text } else { Data::Continue }; + assert_eq!( + frame.header().opcode, + tokio_tungstenite::tungstenite::protocol::frame::coding::OpCode::Data(opcode) + ); + assert_eq!(frame.header().is_final, (i + 1) * MIB >= text.len()); + assert_eq!(frame.payload(), expected); + } + assert!(frames.next().await.is_none()); + } + #[test] fn websocket_config_enables_permessage_deflate() { let config = websocket_config();