From 4473147985c300031f0aa28865cb83a613bd0225 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 10 Feb 2026 19:38:08 -0800 Subject: [PATCH] Do not resend output items in incremental websockets connections (#11383) In the incremental websocket output items are already part of the context, no need to send them again and duplicate. --- codex-rs/Cargo.lock | 360 +++++++++++++----- codex-rs/core/src/client.rs | 127 +++--- .../core/tests/suite/client_websockets.rs | 39 +- 3 files changed, 365 insertions(+), 161 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 4e2877a2c..36ee95747 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" dependencies = [ "rustversion", ] @@ -1227,9 +1227,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -1237,9 +1237,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -1374,7 +1374,7 @@ dependencies = [ "tempfile", "time", "tokio", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", "tracing", "tracing-subscriber", "uuid", @@ -1534,7 +1534,7 @@ dependencies = [ "supports-color 3.0.2", "tempfile", "tokio", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", "tracing", ] @@ -1575,7 +1575,7 @@ dependencies = [ "serde_json", "tempfile", "tokio", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", "tracing", ] @@ -1635,7 +1635,7 @@ dependencies = [ "codex-utils-absolute-path", "pretty_assertions", "serde", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -1728,8 +1728,8 @@ dependencies = [ "tokio", "tokio-tungstenite", "tokio-util", - "toml 0.9.11+spec-1.1.0", - "toml_edit 0.24.0+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", + "toml_edit 0.24.1+spec-1.1.0", "tracing", "tracing-subscriber", "tracing-test", @@ -2304,7 +2304,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", "tracing", "tracing-appender", "tracing-subscriber", @@ -2380,7 +2380,7 @@ version = "0.0.0" dependencies = [ "pretty_assertions", "serde_json", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -3318,9 +3318,9 @@ dependencies = [ [[package]] name = "ena" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" dependencies = [ "log", ] @@ -3640,9 +3640,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "libz-sys", @@ -3975,6 +3975,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "gif" version = "0.14.1" @@ -4001,7 +4014,7 @@ dependencies = [ "bstr", "log", "regex-automata", - "regex-syntax 0.8.8", + "regex-syntax 0.8.9", ] [[package]] @@ -4307,7 +4320,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -4341,14 +4354,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body", @@ -4581,6 +4593,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -4891,9 +4909,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ "jiff-static", "log", @@ -4904,9 +4922,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", @@ -5051,10 +5069,16 @@ dependencies = [ ] [[package]] -name = "libc" -version = "0.2.180" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.181" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "libdbus-sys" @@ -5314,9 +5338,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" @@ -6474,6 +6498,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -6530,16 +6564,16 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bitflags 2.10.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.8", + "regex-syntax 0.8.9", "unarray", ] @@ -6568,9 +6602,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.184" +version = "2.1.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dc6a90669f481b41cae3005c68efa36bef275b95aa9123a7af7f1c68c6e5b2" +checksum = "b033d75bca9da25cfdcd9528de22ed7870d1695b9e1c3ce55b7127a4a2b16fac" dependencies = [ "psl-types", ] @@ -6978,7 +7012,7 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", "x509-parser", ] @@ -7224,25 +7258,25 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.8", + "regex-syntax 0.8.9", ] [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.8", + "regex-syntax 0.8.9", ] [[package]] name = "regex-lite" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" @@ -7252,9 +7286,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" @@ -7302,7 +7336,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -7585,9 +7619,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "salsa20" @@ -7854,10 +7888,11 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f925d575b468e88b079faf590a8dd0c9c99e2ec29e9bab663ceb8b45056312f" +checksum = "d92d893ba7469d361a6958522fa440e4e2bc8bf4c5803cd1bf40b9af63f8f9a8" dependencies = [ + "cfg_aliases 0.2.1", "httpdate", "native-tls", "reqwest", @@ -7874,9 +7909,9 @@ dependencies = [ [[package]] name = "sentry-actix" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18bac0f6b8621fa0f85e298901e51161205788322e1a995e3764329020368058" +checksum = "56cb150fd6b55b3023714a3aaa1e3bdadfd44f164efc54fad69efc69aac36887" dependencies = [ "actix-http", "actix-web", @@ -7887,9 +7922,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb1ef7534f583af20452b1b1bf610a60ed9c8dd2d8485e7bd064efc556a78fb" +checksum = "5f8784d0a27b5cd4b5f75769ffc84f0b7580e3c35e1af9cd83cb90b612d769cc" dependencies = [ "backtrace", "regex", @@ -7898,9 +7933,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd6be899d9938390b6d1ec71e2f53bd9e57b6a9d8b1d5b049e5c364e7da9078" +checksum = "0e5eb42f4cd4f9fdfec9e3b07b25a4c9769df83d218a7e846658984d5948ad3e" dependencies = [ "hostname", "libc", @@ -7912,9 +7947,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ab054c34b87f96c3e4701bea1888317cde30cc7e4a6136d2c48454ab96661c" +checksum = "b0b1e7ca40f965db239da279bf278d87b7407469b98835f27f0c8e59ed189b06" dependencies = [ "rand 0.9.2", "sentry-types", @@ -7925,9 +7960,9 @@ dependencies = [ [[package]] name = "sentry-debug-images" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5637ec550dc6f8c49a711537950722d3fc4baa6fd433c371912104eaff31e2a5" +checksum = "002561e49ea3a9de316e2efadc40fae553921b8ff41448f02ea85fd135a778d6" dependencies = [ "findshlibs", "sentry-core", @@ -7935,9 +7970,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f02c7162f7b69b8de872b439d4696dc1d65f80b13ddd3c3831723def4756b63" +checksum = "8906f8be87aea5ac7ef937323fb655d66607427f61007b99b7cb3504dc5a156c" dependencies = [ "sentry-backtrace", "sentry-core", @@ -7945,9 +7980,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dd47df349a80025819f3d25c3d2f751df705d49c65a4cdc0f130f700972a48" +checksum = "5b07eefe04486316c57aba08ab53dd44753c25102d1d3fe05775cc93a13262d9" dependencies = [ "bitflags 2.10.0", "sentry-backtrace", @@ -7958,9 +7993,9 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.46.1" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecbd63e9d15a26a40675ed180d376fcb434635d2e33de1c24003f61e3e2230d" +checksum = "567711f01f86a842057e1fc17779eba33a336004227e1a1e7e6cc2599e22e259" dependencies = [ "debugid", "hex", @@ -8844,9 +8879,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags 2.10.0", "core-foundation 0.9.4", @@ -8871,12 +8906,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix 1.1.3", "windows-sys 0.61.2", @@ -9261,9 +9296,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap 2.13.0", "serde_core", @@ -9297,9 +9332,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.24.0+spec-1.1.0" +version = "0.24.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c740b185920170a6d9191122cafef7010bd6270a3824594bff6784c04d7f09e" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" dependencies = [ "indexmap 2.13.0", "toml_datetime", @@ -9310,9 +9345,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" dependencies = [ "winnow", ] @@ -9513,9 +9548,9 @@ dependencies = [ [[package]] name = "tracing-test" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +checksum = "19a4c448db514d4f24c5ddb9f73f2ee71bfb24c526cf0c570ba142d1119e0051" dependencies = [ "tracing-core", "tracing-subscriber", @@ -9524,9 +9559,9 @@ dependencies = [ [[package]] name = "tracing-test-macro" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" dependencies = [ "quote", "syn 2.0.114", @@ -9540,7 +9575,7 @@ checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" dependencies = [ "cc", "regex", - "regex-syntax 0.8.8", + "regex-syntax 0.8.9", "serde_json", "streaming-iterator", "tree-sitter-language", @@ -9709,9 +9744,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-linebreak" @@ -9799,9 +9834,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.1.4" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" dependencies = [ "base64 0.22.1", "der", @@ -9958,6 +9993,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasite" version = "0.1.0" @@ -10023,6 +10067,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -10036,6 +10102,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + [[package]] name = "wayland-backend" version = "0.3.12" @@ -10128,9 +10206,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +checksum = "3f00bb839c1cf1e3036066614cbdcd035ecf215206691ea646aa3c60a24f68f2" dependencies = [ "core-foundation 0.10.1", "jni", @@ -10144,9 +10222,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] @@ -10157,14 +10235,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -10807,6 +10885,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.114", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.114", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "wl-clipboard-rs" @@ -11001,18 +11161,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -11126,9 +11286,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" [[package]] name = "zopfli" diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 0999c3291..ffb74315a 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -167,8 +167,7 @@ pub struct ModelClientSession { client: ModelClient, connection: Option, websocket_last_request: Option, - websocket_last_response_id: Option, - websocket_last_response_id_rx: Option>, + websocket_last_response_rx: Option>, /// Turn state for sticky routing. /// /// This is an `OnceLock` that stores the turn state value received from the server @@ -182,6 +181,12 @@ pub struct ModelClientSession { turn_state: Arc>, } +#[derive(Debug, Clone)] +struct LastResponse { + response_id: String, + items_added: Vec, +} + enum WebsocketStreamOutcome { Stream(ResponseStream), FallbackToHttp, @@ -231,8 +236,7 @@ impl ModelClient { client: self.clone(), connection: None, websocket_last_request: None, - websocket_last_response_id: None, - websocket_last_response_id_rx: None, + websocket_last_response_rx: None, turn_state: Arc::new(OnceLock::new()), } } @@ -531,10 +535,15 @@ impl ModelClientSession { } } - fn get_incremental_items(&self, request: &ResponsesApiRequest) -> Option> { + fn get_incremental_items( + &self, + request: &ResponsesApiRequest, + last_response: Option<&LastResponse>, + ) -> Option> { // Checks whether the current request is an incremental append to the previous request. // We only append when non-input request fields are unchanged and `input` is a strict - // extension of the previous input. + // extension of the previous known input. Server-returned output items are treated as part + // of the baseline so we do not resend them. let previous_request = self.websocket_last_request.as_ref()?; let mut previous_without_input = previous_request.clone(); previous_without_input.input.clear(); @@ -544,38 +553,29 @@ impl ModelClientSession { return None; } - let previous_len = previous_request.input.len(); - if previous_len > 0 - && request.input.starts_with(&previous_request.input) - && previous_len < request.input.len() + let mut baseline = previous_request.input.clone(); + if let Some(last_response) = last_response { + baseline.extend(last_response.items_added.clone()); + } + + let baseline_len = baseline.len(); + if baseline_len > 0 + && request.input.starts_with(&baseline) + && baseline_len < request.input.len() { - Some(request.input[previous_len..].to_vec()) + Some(request.input[baseline_len..].to_vec()) } else { None } } - fn refresh_websocket_last_response_id(&mut self) { - if let Some(mut receiver) = self.websocket_last_response_id_rx.take() { - match receiver.try_recv() { - Ok(response_id) if !response_id.is_empty() => { - self.websocket_last_response_id = Some(response_id); - } - Ok(_) | Err(TryRecvError::Closed) => { - self.websocket_last_response_id = None; - } - Err(TryRecvError::Empty) => { - self.websocket_last_response_id_rx = Some(receiver); - } - } - } - } - - fn websocket_previous_response_id(&mut self) -> Option { - self.refresh_websocket_last_response_id(); - self.websocket_last_response_id - .clone() - .filter(|id| !id.is_empty()) + fn get_last_response(&mut self) -> Option { + self.websocket_last_response_rx + .take() + .and_then(|mut receiver| match receiver.try_recv() { + Ok(last_response) => Some(last_response), + Err(TryRecvError::Closed) | Err(TryRecvError::Empty) => None, + }) } fn prepare_websocket_request( @@ -583,11 +583,15 @@ impl ModelClientSession { payload: ResponseCreateWsRequest, request: &ResponsesApiRequest, ) -> ResponsesWsRequest { + let last_response = self.get_last_response(); let responses_websockets_v2_enabled = self.client.responses_websockets_v2_enabled(); - let incremental_items = self.get_incremental_items(request); + let incremental_items = self.get_incremental_items(request, last_response.as_ref()); if let Some(append_items) = incremental_items { if responses_websockets_v2_enabled - && let Some(previous_response_id) = self.websocket_previous_response_id() + && let Some(previous_response_id) = last_response + .as_ref() + .map(|last_response| last_response.response_id.clone()) + .filter(|id| !id.is_empty()) { let payload = ResponseCreateWsRequest { previous_response_id: Some(previous_response_id), @@ -660,8 +664,7 @@ impl ModelClientSession { if needs_new { self.websocket_last_request = None; - self.websocket_last_response_id = None; - self.websocket_last_response_id_rx = None; + self.websocket_last_response_rx = None; let turn_state = options .turn_state .clone() @@ -716,7 +719,8 @@ impl ModelClientSession { self.client.state.provider.stream_idle_timeout(), ) .map_err(map_api_error)?; - return Ok(map_response_stream(stream, otel_manager.clone())); + let (stream, _last_request_rx) = map_response_stream(stream, otel_manager.clone()); + return Ok(stream); } let auth_manager = self.client.state.auth_manager.clone(); @@ -747,7 +751,8 @@ impl ModelClientSession { match stream_result { Ok(stream) => { - return Ok(map_response_stream(stream, otel_manager.clone())); + let (stream, _) = map_response_stream(stream, otel_manager.clone()); + return Ok(stream); } Err(ApiError::Transport( unauthorized_transport @ TransportError::Http { status, .. }, @@ -829,22 +834,11 @@ impl ModelClientSession { .await .map_err(map_api_error)?; self.websocket_last_request = Some(request); - let (last_response_id_sender, last_response_id_receiver) = oneshot::channel(); - self.websocket_last_response_id_rx = Some(last_response_id_receiver); - let mut last_response_id_sender = Some(last_response_id_sender); - let stream_result = stream_result.inspect(move |event| { - if let Ok(ResponseEvent::Completed { response_id, .. }) = event - && !response_id.is_empty() - && let Some(sender) = last_response_id_sender.take() - { - let _ = sender.send(response_id.clone()); - } - }); + let (stream, last_request_rx) = + map_response_stream(stream_result, otel_manager.clone()); + self.websocket_last_response_rx = Some(last_request_rx); - return Ok(WebsocketStreamOutcome::Stream(map_response_stream( - stream_result, - otel_manager.clone(), - ))); + return Ok(WebsocketStreamOutcome::Stream(stream)); } } @@ -942,6 +936,7 @@ impl ModelClientSession { self.connection = None; self.websocket_last_request = None; + self.websocket_last_response_rx = None; } activated } @@ -986,7 +981,10 @@ fn build_responses_headers( headers } -fn map_response_stream(api_stream: S, otel_manager: OtelManager) -> ResponseStream +fn map_response_stream( + api_stream: S, + otel_manager: OtelManager, +) -> (ResponseStream, oneshot::Receiver) where S: futures::Stream> + Unpin @@ -994,12 +992,25 @@ where + 'static, { let (tx_event, rx_event) = mpsc::channel::>(1600); + let (tx_last_response, rx_last_response) = oneshot::channel::(); tokio::spawn(async move { let mut logged_error = false; + let mut tx_last_response = Some(tx_last_response); + let mut items_added: Vec = Vec::new(); let mut api_stream = api_stream; while let Some(event) = api_stream.next().await { match event { + Ok(ResponseEvent::OutputItemDone(item)) => { + items_added.push(item.clone()); + if tx_event + .send(Ok(ResponseEvent::OutputItemDone(item))) + .await + .is_err() + { + return; + } + } Ok(ResponseEvent::Completed { response_id, token_usage, @@ -1013,6 +1024,12 @@ where usage.total_tokens, ); } + if let Some(sender) = tx_last_response.take() { + let _ = sender.send(LastResponse { + response_id: response_id.clone(), + items_added: std::mem::take(&mut items_added), + }); + } if tx_event .send(Ok(ResponseEvent::Completed { response_id, @@ -1043,7 +1060,7 @@ where } }); - ResponseStream { rx_event } + (ResponseStream { rx_event }, rx_last_response) } /// Handles a 401 response by optionally refreshing ChatGPT tokens once. diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index c4f63f844..18527ce71 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -29,6 +29,7 @@ use codex_protocol::user_input::UserInput; use core_test_support::load_default_config_for_test; use core_test_support::responses::WebSocketConnectionConfig; use core_test_support::responses::WebSocketTestServer; +use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_response_created; use core_test_support::responses::start_websocket_server; @@ -570,7 +571,11 @@ async fn responses_websocket_appends_on_prefix() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![ - vec![ev_response_created("resp-1"), ev_completed("resp-1")], + vec![ + ev_response_created("resp-1"), + ev_assistant_message("msg-1", "assistant output"), + ev_completed("resp-1"), + ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) .await; @@ -578,7 +583,11 @@ async fn responses_websocket_appends_on_prefix() { let harness = websocket_harness(&server).await; let mut client_session = harness.client.new_session(); let prompt_one = prompt_with_input(vec![message_item("hello")]); - let prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]); + let prompt_two = prompt_with_input(vec![ + message_item("hello"), + assistant_message_item("msg-1", "assistant output"), + message_item("second"), + ]); stream_until_complete(&mut client_session, &harness, &prompt_one).await; stream_until_complete(&mut client_session, &harness, &prompt_two).await; @@ -594,7 +603,7 @@ async fn responses_websocket_appends_on_prefix() { assert_eq!(first["input"].as_array().map(Vec::len), Some(1)); let expected_append = serde_json::json!({ "type": "response.append", - "input": serde_json::to_value(&prompt_two.input[1..]).expect("serialize append items"), + "input": serde_json::to_value(&prompt_two.input[2..]).expect("serialize append items"), }); assert_eq!(second, expected_append); @@ -675,7 +684,11 @@ async fn responses_websocket_v2_creates_with_previous_response_id_on_prefix() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![ - vec![ev_response_created("resp-1"), ev_completed("resp-1")], + vec![ + ev_response_created("resp-1"), + ev_assistant_message("msg-1", "assistant output"), + ev_completed("resp-1"), + ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) .await; @@ -683,7 +696,11 @@ async fn responses_websocket_v2_creates_with_previous_response_id_on_prefix() { let harness = websocket_harness_with_v2(&server, true).await; let mut session = harness.client.new_session(); let prompt_one = prompt_with_input(vec![message_item("hello")]); - let prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]); + let prompt_two = prompt_with_input(vec![ + message_item("hello"), + assistant_message_item("msg-1", "assistant output"), + message_item("second"), + ]); stream_until_complete(&mut session, &harness, &prompt_one).await; stream_until_complete(&mut session, &harness, &prompt_two).await; @@ -698,7 +715,7 @@ async fn responses_websocket_v2_creates_with_previous_response_id_on_prefix() { assert_eq!(second["previous_response_id"].as_str(), Some("resp-1")); assert_eq!( second["input"], - serde_json::to_value(&prompt_two.input[1..]).unwrap() + serde_json::to_value(&prompt_two.input[2..]).unwrap() ); server.shutdown().await; @@ -876,6 +893,16 @@ fn message_item(text: &str) -> ResponseItem { } } +fn assistant_message_item(id: &str, text: &str) -> ResponseItem { + ResponseItem::Message { + id: Some(id.to_string()), + role: "assistant".into(), + content: vec![ContentItem::OutputText { text: text.into() }], + end_turn: None, + phase: None, + } +} + fn prompt_with_input(input: Vec) -> Prompt { let mut prompt = Prompt::default(); prompt.input = input;