diff --git a/Cargo.lock b/Cargo.lock index aa4989a..744e420 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,25 +55,31 @@ dependencies = [ [[package]] name = "age" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf640be7658959746f1f0f2faab798f6098a9436a8e18e148d18bc9875e13c4b" +checksum = "fd290633c2482479f70f6d1d96ae0e9f52c6a26cd5859edd47ee1fe33fc89f26" dependencies = [ "age-core", - "base64 0.21.7", + "base64 0.22.1", "bech32", "chacha20poly1305", + "cipher", "cookie-factory", + "hkdf 0.12.4", "hmac 0.12.1", + "hpke", "i18n-embed", "i18n-embed-fl", "lazy_static", - "nom 7.1.3", + "ml-kem", + "nom 8.0.0", + "p256", "pin-project", "rand 0.8.6", "rust-embed", "scrypt", "sha2 0.10.9", + "sha3", "subtle", "x25519-dalek", "zeroize", @@ -81,16 +87,18 @@ dependencies = [ [[package]] name = "age-core" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99" +checksum = "01d4375964d1501e5f1b32aef2ead573913893ff238448d4e9fdf1522d828656" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", + "bech32", "chacha20poly1305", "cookie-factory", "hkdf 0.12.4", + "hpke", "io_tee", - "nom 7.1.3", + "nom 8.0.0", "rand 0.8.6", "secrecy", "sha2 0.10.9", @@ -202,7 +210,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -213,7 +221,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -990,12 +998,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -1035,9 +1037,9 @@ dependencies = [ [[package]] name = "bech32" -version = "0.9.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "beef" @@ -1461,8 +1463,8 @@ dependencies = [ [[package]] name = "codex-agent-identity" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "base64 0.22.1", @@ -1481,7 +1483,7 @@ dependencies = [ [[package]] name = "codex-api" -version = "0.160.0" +version = "0.162.1" dependencies = [ "anyhow", "assert_matches", @@ -1560,8 +1562,8 @@ dependencies = [ [[package]] name = "codex-async-utils" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "rand 0.9.3", "tokio", @@ -1570,8 +1572,8 @@ dependencies = [ [[package]] name = "codex-aws-auth" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "aws-config", "aws-credential-types", @@ -1590,8 +1592,8 @@ dependencies = [ [[package]] name = "codex-build-info" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-install-context", "semver", @@ -1601,8 +1603,8 @@ dependencies = [ [[package]] name = "codex-client" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-http-client", "eventsource-stream", @@ -1616,8 +1618,8 @@ dependencies = [ [[package]] name = "codex-code-mode" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-code-mode-protocol", "codex-http-client", @@ -1639,8 +1641,8 @@ dependencies = [ [[package]] name = "codex-code-mode-protocol" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-protocol", "glob", @@ -1657,13 +1659,13 @@ dependencies = [ [[package]] name = "codex-collaboration-mode-templates" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" [[package]] name = "codex-config" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "base64 0.22.1", @@ -1704,13 +1706,13 @@ dependencies = [ "tracing", "wildmatch", "winapi-util", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "codex-connectors" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "arc-swap", @@ -1731,8 +1733,8 @@ dependencies = [ [[package]] name = "codex-context-fragments" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-protocol", "codex-utils-string", @@ -1741,8 +1743,8 @@ dependencies = [ [[package]] name = "codex-core-plugin-common" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-utils-absolute-path", "semver", @@ -1753,8 +1755,8 @@ dependencies = [ [[package]] name = "codex-exec-server" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "arc-swap", "axum", @@ -1803,16 +1805,17 @@ dependencies = [ "tracing", "url", "uuid", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "codex-exec-server-protocol" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "base64 0.22.1", "codex-file-system", + "codex-install-context", "codex-mxc-sandbox", "codex-network-proxy", "codex-protocol", @@ -1824,8 +1827,8 @@ dependencies = [ [[package]] name = "codex-execpolicy" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "clap", @@ -1842,8 +1845,8 @@ dependencies = [ [[package]] name = "codex-extension-items" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-utils-absolute-path", "schemars 0.8.22", @@ -1854,11 +1857,12 @@ dependencies = [ [[package]] name = "codex-features" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-network-proxy", "codex-protocol", + "codex-utils-redacted-string", "schemars 0.8.22", "serde", "toml 0.9.11+spec-1.1.0", @@ -1867,8 +1871,8 @@ dependencies = [ [[package]] name = "codex-feedback" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "bytes", @@ -1883,6 +1887,8 @@ dependencies = [ "mime_guess", "sentry", "serde_json", + "tar", + "tempfile", "tokio", "tracing", "tracing-subscriber", @@ -1890,8 +1896,8 @@ dependencies = [ [[package]] name = "codex-file-search" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "clap", @@ -1905,8 +1911,8 @@ dependencies = [ [[package]] name = "codex-file-system" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "bytes", "codex-protocol", @@ -1918,8 +1924,8 @@ dependencies = [ [[package]] name = "codex-file-watcher" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "notify", "tokio", @@ -1928,8 +1934,8 @@ dependencies = [ [[package]] name = "codex-git-utils" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "chrono", @@ -1955,19 +1961,20 @@ dependencies = [ [[package]] name = "codex-guardian-context" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-context-fragments", "codex-history", "codex-protocol", + "serde", "serde_json", ] [[package]] name = "codex-history" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-protocol", "schemars 0.8.22", @@ -1977,8 +1984,8 @@ dependencies = [ [[package]] name = "codex-http-client" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "bytes", "codex-utils-rustls-provider", @@ -2002,14 +2009,14 @@ dependencies = [ "tokio", "tracing", "tracing-opentelemetry", - "windows-sys 0.52.0", + "windows-sys 0.61.2", "zstd", ] [[package]] name = "codex-install-context" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-utils-absolute-path", "codex-utils-home-dir", @@ -2020,8 +2027,8 @@ dependencies = [ [[package]] name = "codex-keyring-store" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "keyring", "secret-service", @@ -2031,9 +2038,10 @@ dependencies = [ [[package]] name = "codex-login" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ + "anyhow", "base64 0.22.1", "chrono", "codex-agent-identity", @@ -2066,8 +2074,8 @@ dependencies = [ [[package]] name = "codex-model-provider" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "chrono", "codex-agent-identity", @@ -2095,8 +2103,8 @@ dependencies = [ [[package]] name = "codex-model-provider-info" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-client", "codex-protocol", @@ -2109,8 +2117,8 @@ dependencies = [ [[package]] name = "codex-models-manager" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "chrono", "codex-http-client", @@ -2127,8 +2135,8 @@ dependencies = [ [[package]] name = "codex-mxc-sandbox" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "appcontainer_common", @@ -2148,8 +2156,8 @@ dependencies = [ [[package]] name = "codex-network-proxy" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "base64 0.22.1", @@ -2190,13 +2198,13 @@ dependencies = [ "tracing", "tracing-subscriber", "url", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "codex-otel" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "bytes", "chrono", @@ -2230,8 +2238,8 @@ dependencies = [ [[package]] name = "codex-plugin" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-config", "codex-core-plugin-common", @@ -2245,8 +2253,8 @@ dependencies = [ [[package]] name = "codex-prompts" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "codex-collaboration-mode-templates", @@ -2262,8 +2270,8 @@ dependencies = [ [[package]] name = "codex-protocol" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "chardetng", "chrono", @@ -2277,6 +2285,7 @@ dependencies = [ "codex-utils-path-uri", "codex-utils-redacted-string", "codex-utils-string", + "derive_more 2.1.1", "encoding_rs", "gix-url", "globset", @@ -2304,8 +2313,8 @@ dependencies = [ [[package]] name = "codex-response-debug-context" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "base64 0.22.1", "codex-api", @@ -2315,8 +2324,8 @@ dependencies = [ [[package]] name = "codex-rollout" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "chrono", @@ -2328,8 +2337,11 @@ dependencies = [ "codex-protocol", "codex-state", "codex-utils-absolute-path", + "codex-utils-output-truncation", "codex-utils-path", "codex-utils-process", + "codex-utils-string", + "memchr", "regex", "serde", "serde_json", @@ -2343,8 +2355,8 @@ dependencies = [ [[package]] name = "codex-sandboxing" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "codex-mxc-sandbox", @@ -2369,8 +2381,8 @@ dependencies = [ [[package]] name = "codex-secrets" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "age", "anyhow", @@ -2388,8 +2400,8 @@ dependencies = [ [[package]] name = "codex-shell-command" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "base64 0.22.1", "codex-protocol", @@ -2409,8 +2421,8 @@ dependencies = [ [[package]] name = "codex-skills" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-protocol", "codex-shell-command", @@ -2426,14 +2438,15 @@ dependencies = [ [[package]] name = "codex-state" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "chrono", "codex-history", "codex-protocol", "codex-utils-absolute-path", + "file-id", "libsqlite3-sys", "log", "serde", @@ -2450,16 +2463,16 @@ dependencies = [ [[package]] name = "codex-terminal-detection" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "tracing", ] [[package]] name = "codex-tools" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "bitflags 2.13.1", "codex-code-mode", @@ -2483,21 +2496,21 @@ dependencies = [ [[package]] name = "codex-uds" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "async-io", "libc", "tokio", "tokio-util", "uds_windows", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "codex-utils-absolute-path" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "dirs", "dunce", @@ -2508,8 +2521,8 @@ dependencies = [ [[package]] name = "codex-utils-cache" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "lru", "sha1 0.10.6", @@ -2518,8 +2531,8 @@ dependencies = [ [[package]] name = "codex-utils-home-dir" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-utils-absolute-path", "dirs", @@ -2527,8 +2540,8 @@ dependencies = [ [[package]] name = "codex-utils-image" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "base64 0.22.1", "codex-utils-cache", @@ -2541,28 +2554,29 @@ dependencies = [ [[package]] name = "codex-utils-output-truncation" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-protocol", "codex-utils-string", + "serde_json", ] [[package]] name = "codex-utils-path" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-utils-absolute-path", "dunce", "tempfile", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "codex-utils-path-uri" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "base64 0.22.1", "codex-utils-absolute-path", @@ -2576,8 +2590,8 @@ dependencies = [ [[package]] name = "codex-utils-plugins" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-exec-server", "codex-exec-server-protocol", @@ -2589,13 +2603,13 @@ dependencies = [ [[package]] name = "codex-utils-process" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" [[package]] name = "codex-utils-pty" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "filedescriptor", @@ -2610,8 +2624,8 @@ dependencies = [ [[package]] name = "codex-utils-redacted-string" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "schemars 0.8.22", "serde", @@ -2619,21 +2633,21 @@ dependencies = [ [[package]] name = "codex-utils-rustls-provider" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "rustls", ] [[package]] name = "codex-utils-stream-parser" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" [[package]] name = "codex-utils-string" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "regex-lite", "serde", @@ -2642,13 +2656,13 @@ dependencies = [ [[package]] name = "codex-utils-template" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" [[package]] name = "codex-websocket-auth" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "clap", @@ -2663,8 +2677,8 @@ dependencies = [ [[package]] name = "codex-websocket-client" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-http-client", "futures", @@ -2677,8 +2691,8 @@ dependencies = [ [[package]] name = "codex-windows-sandbox" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "anyhow", "base64 0.22.1", @@ -2700,13 +2714,13 @@ dependencies = [ "tokio", "tracing-appender", "windows 0.58.0", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "codex-workload-identity" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.1" +source = "git+https://github.com/openai/codex.git?rev=092d3acd6bec3e3a14bdc7e7a2810ab628ab759d#092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" dependencies = [ "codex-http-client", "serde", @@ -3302,7 +3316,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl 2.1.1", ] [[package]] @@ -3318,6 +3341,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", + "unicode-xid", +] + [[package]] name = "diff" version = "0.1.13" @@ -3544,6 +3580,7 @@ dependencies = [ "ff", "generic-array", "group", + "hkdf 0.12.4", "pem-rfc7468", "pkcs8", "rand_core 0.6.4", @@ -3800,6 +3837,15 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "filedescriptor" version = "0.8.3" @@ -3877,9 +3923,9 @@ dependencies = [ [[package]] name = "fluent" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477" dependencies = [ "fluent-bundle", "unic-langid", @@ -3887,16 +3933,16 @@ dependencies = [ [[package]] name = "fluent-bundle" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" dependencies = [ "fluent-langneg", "fluent-syntax", "intl-memoizer", "intl_pluralrules", - "rustc-hash 1.1.0", - "self_cell 0.10.3", + "rustc-hash", + "self_cell", "smallvec", "unic-langid", ] @@ -3912,11 +3958,12 @@ dependencies = [ [[package]] name = "fluent-syntax" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ - "thiserror 1.0.69", + "memchr", + "thiserror 2.0.18", ] [[package]] @@ -5346,6 +5393,26 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "hpke" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4917627a14198c3603282c5158b815ad5534795451d3c074b53cf3cee0960b11" +dependencies = [ + "aead", + "aes-gcm", + "chacha20poly1305", + "digest 0.10.7", + "generic-array", + "hkdf 0.12.4", + "hmac 0.12.1", + "p256", + "rand_core 0.6.4", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "http" version = "0.2.12" @@ -5548,9 +5615,9 @@ dependencies = [ [[package]] name = "i18n-embed" -version = "0.15.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "669ffc2c93f97e6ddf06ddbe999fcd6782e3342978bb85f7d3c087c7978404c4" +checksum = "a217bbb075dcaefb292efa78897fc0678245ca67f265d12c351e42268fcb0305" dependencies = [ "arc-swap", "fluent", @@ -5568,16 +5635,16 @@ dependencies = [ [[package]] name = "i18n-embed-fl" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04b2969d0b3fc6143776c535184c19722032b43e6a642d710fa3f88faec53c2d" +checksum = "602e6bd30c3db2749e13e38b363a3d98d9d41de1d8de7a79c31bb69e45b47cda" dependencies = [ "find-crate", "fluent", "fluent-syntax", "i18n-config", "i18n-embed", - "proc-macro-error2", + "proc-macro-error3", "proc-macro2", "quote", "strsim 0.11.1", @@ -7676,25 +7743,25 @@ dependencies = [ ] [[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" +name = "proc-macro-error-attr3" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +checksum = "9e564d14133360e1ae169ffde5da25881b5fa47261665b8e5713c212c27799da" dependencies = [ "proc-macro2", "quote", ] [[package]] -name = "proc-macro-error2" -version = "2.0.1" +name = "proc-macro-error3" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +checksum = "8f0d4471b3436c22106b21913b1dda531558918ae9b7ec55d58aa84b43552233" dependencies = [ - "proc-macro-error-attr2", + "proc-macro-error-attr3", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -7925,7 +7992,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "socket2 0.5.10", "thiserror 2.0.18", @@ -7945,7 +8012,7 @@ dependencies = [ "lru-slab", "rand 0.9.3", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "rustls-pki-types", "slab", @@ -7966,7 +8033,7 @@ dependencies = [ "once_cell", "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -8628,9 +8695,8 @@ dependencies = [ [[package]] name = "rmcp" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b6914fac0be956fe704a38239c3f44a9f841d1b06a5713d2f638065593f5b5" +version = "3.3.0" +source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3e636cab26c013eca5131103c03d20237f12c4df#3e636cab26c013eca5131103c03d20237f12c4df" dependencies = [ "base64 0.23.0", "chrono", @@ -8651,9 +8717,8 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf1c49bd4d52014b94db0877410db273c2008f01628b0252a2e9460ad9b7fda" +version = "3.3.0" +source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3e636cab26c013eca5131103c03d20237f12c4df#3e636cab26c013eca5131103c03d20237f12c4df" dependencies = [ "darling 0.24.0", "proc-macro2", @@ -8696,12 +8761,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -9099,15 +9158,6 @@ dependencies = [ "libc", ] -[[package]] -name = "self_cell" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" -dependencies = [ - "self_cell 1.2.2", -] - [[package]] name = "self_cell" version = "1.2.2" @@ -9548,7 +9598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -9784,7 +9834,7 @@ dependencies = [ "dashmap", "debugserver-types", "derivative", - "derive_more", + "derive_more 1.0.0", "display_container", "dupe", "either", @@ -9853,7 +9903,7 @@ dependencies = [ "annotate-snippets", "anyhow", "derivative", - "derive_more", + "derive_more 1.0.0", "dupe", "logos", "lsp-types", @@ -10065,6 +10115,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -10749,7 +10809,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash 2.1.1", + "rustc-hash", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 266290a..07a03c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ license = "Apache-2.0" rust-version = "1.95" [dependencies] -codex-context-fragments = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-websocket-client = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-context-fragments = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-websocket-client = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } tokio-tungstenite = "0.28" zstd = "0.13" anyhow = "1" @@ -27,22 +27,22 @@ toml = "0.9.5" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } uuid = { version = "1", features = ["v4", "v5"] } -codex-api = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-client = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-http-client = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-login = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-model-provider = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-model-provider-info = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-models-manager = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-protocol = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-config = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-api = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-client = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-http-client = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-login = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-model-provider = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-model-provider-info = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-models-manager = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-protocol = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-config = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } -codex-features = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-features = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } -codex-tools = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-tools = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } -codex-exec-server = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-exec-server = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } [dev-dependencies] reqwest = { version = "0.12", features = ["json", "stream", "multipart"] } diff --git a/README.md b/README.md index ae9da4f..c788fb4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A gateway that uses your ChatGPT subscription through the Codex Rust libraries. Supports Responses, streaming, WebSocket, images, files, search and Realtime. -Built on Codex **0.160.0** and Rust **1.95.0**. +Built on Codex **0.162.1** and Rust **1.95.0**. The gateway loads your account's model catalog at startup, including `gpt-6-sol` and `gpt-6-luna` when available. Restart to refresh the catalog; diff --git a/baseline/codex-release.lock.json b/baseline/codex-release.lock.json index 4ca86c3..1787617 100644 --- a/baseline/codex-release.lock.json +++ b/baseline/codex-release.lock.json @@ -1,29 +1,29 @@ { "repository": "https://github.com/openai/codex.git", - "release": "0.160.0", - "tag": "rust-v0.160.0", - "tag_object": "79b1b666f2e8551f8abbbca34957227f67f3f553", - "commit": "a956835d020762cb2b570053af06f643a11c0ecc", - "published_at": "2026-10-01T20:19:13Z", - "release_url": "https://github.com/openai/codex/releases/tag/rust-v0.160.0", + "release": "0.162.1", + "tag": "rust-v0.162.1", + "tag_object": "7a078f6bb4ce0dc2a81408b334ce52979d92c603", + "commit": "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d", + "published_at": "2026-10-09T19:44:51Z", + "release_url": "https://github.com/openai/codex/releases/tag/rust-v0.162.1", "prerelease": false, "companions": [ { "asset": "bwrap-x86_64-unknown-linux-musl.tar.gz", - "url": "https://github.com/openai/codex/releases/download/rust-v0.160.0/bwrap-x86_64-unknown-linux-musl.tar.gz", - "sha256": "1844535ea16d5454a6b5ee5df541c068ea8ddf1028ad3520255b16e05de3183d", + "url": "https://github.com/openai/codex/releases/download/rust-v0.162.1/bwrap-x86_64-unknown-linux-musl.tar.gz", + "sha256": "fa78980959466bd7a04643384c6293511f06a3140345c1237cfc75ccde7f3eea", "executable": "codex-resources/bwrap" }, { "asset": "codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz", - "url": "https://github.com/openai/codex/releases/download/rust-v0.160.0/codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz", - "sha256": "ac6cd6288f0e39f46a33eba1cfd37711651f14f33dbc73d6a1b82eb45e038cac", + "url": "https://github.com/openai/codex/releases/download/rust-v0.162.1/codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz", + "sha256": "458239ea45c1fc508d89695f180ab352f2834709a8a46929a4466f4f04399ada", "executable": "codex-code-mode-host" } ], "binary": { "asset": "codex-x86_64-unknown-linux-musl.tar.gz", - "url": "https://github.com/openai/codex/releases/download/rust-v0.160.0/codex-x86_64-unknown-linux-musl.tar.gz", - "sha256": "306865417d4ee7a927785852910a527f41e1e159add390ac5ae3accb67d44a13" + "url": "https://github.com/openai/codex/releases/download/rust-v0.162.1/codex-x86_64-unknown-linux-musl.tar.gz", + "sha256": "86f268d81b898f3e144c5ecff2ad2fda2c5802e042d6195b72538fe0fa125057" } } diff --git a/docs/codex-models.md b/docs/codex-models.md index 20ea51b..85a30e3 100644 --- a/docs/codex-models.md +++ b/docs/codex-models.md @@ -5,9 +5,9 @@ | Query parameter | Type / behavior | | --- | --- | -| `client_version` | Optional string; defaults to `0.160.0`. At most 128 ASCII letters, digits, `.`, `-`, `_`, `+`. | +| `client_version` | Optional string; defaults to `0.162.1`. At most 128 ASCII letters, digits, `.`, `-`, `_`, `+`. | -Example: `/backend-api/codex/models?client_version=0.160.0`. +Example: `/backend-api/codex/models?client_version=0.162.1`. The original `ModelsClient` fetches the catalog using the gateway's subscription. The complete upstream JSON is preserved, including fields outside the pinned model schema: diff --git a/docs/codex-responses.md b/docs/codex-responses.md index 426f569..4ece386 100644 --- a/docs/codex-responses.md +++ b/docs/codex-responses.md @@ -8,7 +8,7 @@ Send the native request built by Codex. `model` must be a string and `stream` must be `true`: ```json -{"model":"gpt-5.5","instructions":"You are a coding assistant.","input":[{"role":"user","content":[{"type":"input_text","text":"Hello"}]}],"tools":[],"tool_choice":"auto","store":false,"stream":true} +{"model":"gpt-5.5","input":[{"role":"developer","content":[{"type":"input_text","text":"You are a coding assistant."}]},{"role":"user","content":[{"type":"input_text","text":"Hello"}]}],"tools":[],"tool_choice":"auto","store":false,"stream":true} ``` Native JSON, tool namespaces, future fields and Responses Lite payloads are preserved. The gateway does not apply public catalog validation to `model`; the upstream validates native requests. `Content-Encoding` accepts `identity` or `zstd`. @@ -19,6 +19,6 @@ Codex session/routing metadata is forwarded, including `session-id`, `thread-id` Always SSE with native event payloads, including unknown event fields. Unlike `/v1/responses`, this endpoint does not assemble or rewrite terminal `response.output`. The original Codex parser validates the stream; interrupted streams never become synthetic success responses. -Codex 0.160.0 performs remote compaction through native Responses using a `compaction_trigger` input item. The separate [compact facade](responses-compact.md) is for applications that want a single JSON result. +Codex 0.162.1 performs remote compaction through native Responses using a `compaction_trigger` input item. The separate [compact facade](responses-compact.md) is for applications that want a single JSON result. Local shell, filesystem, custom and MCP tools still execute in the calling Codex. Native [images](images-generations.md) and [standalone search](search.md) use their corresponding gateway routes. diff --git a/docs/common.md b/docs/common.md index 76635ad..2583f52 100644 --- a/docs/common.md +++ b/docs/common.md @@ -67,4 +67,4 @@ Gateway errors use this shape; `message` describes the failure: Upstream HTTP failures preserve their status and body, which may be non-JSON, plus headers such as `Retry-After`; transparent native routes preserve all end-to-end headers. Once SSE or WebSocket starts, failures are conveyed as events or connection closure; HTTP 200 alone does not mean generation succeeded. -Public adapters reject unsupported parameters instead of silently pretending to implement them. Native Responses preserves unknown protocol fields; native images/search use the pinned Codex types. Compatibility targets Codex **0.160.0**, not every OpenAI service or future Codex release. +Public adapters reject unsupported parameters instead of silently pretending to implement them. Native Responses preserves unknown protocol fields; native images/search use the pinned Codex types. Compatibility targets Codex **0.162.1**, not every OpenAI service or future Codex release. diff --git a/docs/health.md b/docs/health.md index 37dc9ea..1490127 100644 --- a/docs/health.md +++ b/docs/health.md @@ -3,7 +3,7 @@ `GET /healthz` · No authentication, body or query parameters required. ```json -{"status":"ok","codex_release":"0.160.0","codex_revision":"687a119f0fcaace47e1f1abcc77cec6c813fd6da"} +{"status":"ok","codex_release":"0.162.1","codex_revision":"092d3acd6bec3e3a14bdc7e7a2810ab628ab759d"} ``` HTTP 200 confirms the gateway is running and reports its pinned source version. It does not call the upstream or guarantee current subscription validity, quota or model availability. diff --git a/docs/index.md b/docs/index.md index 055c78d..8ede395 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ [Common rules](common.md) · [Native routing and credentials](native.md) · [Pinned inventory](../baseline/api-inventory.json) · [Official counterpart mapping](../baseline/openai-api-counterparts.json) -Each page describes one logical endpoint and its aliases. The inventory is a snapshot of first-party routes audited against Codex 0.155.1; the gateway now uses 0.160.0. Transparent forwarding does not establish entitlement or full public API compatibility. `field?` means optional/nullable in the inventory's client revision; server-owned and additional fields are preserved. +Each page describes one logical endpoint and its aliases. The inventory is a snapshot of first-party routes audited against Codex 0.155.1; the gateway now uses 0.162.1. Transparent forwarding does not establish entitlement or full public API compatibility. `field?` means optional/nullable in the inventory's client revision; server-owned and additional fields are preserved. ## Public adapters diff --git a/docs/models.md b/docs/models.md index b18eab9..5a3b2de 100644 --- a/docs/models.md +++ b/docs/models.md @@ -8,7 +8,7 @@ No request body or required query parameters. When `client_version` is present ( {"object":"list","data":[{"id":"gpt-5.5","object":"model","created":0,"owned_by":"openai"}]} ``` -At startup, the gateway fetches the current account catalog using Codex 0.160.0. Public Responses (HTTP and WebSocket) and compaction validate model names and capabilities against this snapshot, so newly available models such as `gpt-6-sol` and `gpt-6-luna` do not require a new bundled catalog. Restart the gateway to refresh it. +At startup, the gateway fetches the current account catalog using Codex 0.162.1. Public Responses (HTTP and WebSocket) and compaction validate model names and capabilities against this snapshot, so newly available models such as `gpt-6-sol` and `gpt-6-luna` do not require a new bundled catalog. Restart the gateway to refresh it. If fetching fails, times out (after at most 30 seconds), or returns an empty/invalid catalog, startup logs a warning and falls back to the pinned Codex bundled catalog. Models absent from that fallback remain unavailable through public Responses. This is not a complete image-model or OpenAI Platform model directory; image endpoints default to `gpt-image-2` separately. diff --git a/docs/native.md b/docs/native.md index b5b9ea8..d5c8b28 100644 --- a/docs/native.md +++ b/docs/native.md @@ -1,6 +1,6 @@ # Native routing -The gateway uses Codex **0.160.0** and retains the native route inventory audited against **0.155.1**. Source anchors in the endpoint pages refer to that inventory snapshot; upgrading the client does not establish continued upstream availability for every server-owned route. See the [endpoint index](index.md), [source inventory](../baseline/api-inventory.json) and [official API comparison](../baseline/openai-api-counterparts.json). These routes relay server-owned contracts; account entitlement and upstream validation still apply. +The gateway uses Codex **0.162.1** and retains the native route inventory audited against **0.155.1**. Source anchors in the endpoint pages refer to that inventory snapshot; upgrading the client does not establish continued upstream availability for every server-owned route. See the [endpoint index](index.md), [source inventory](../baseline/api-inventory.json) and [official API comparison](../baseline/openai-api-counterparts.json). These routes relay server-owned contracts; account entitlement and upstream validation still apply. | Gateway prefix | Upstream prefix | | --- | --- | diff --git a/docs/realtime.md b/docs/realtime.md index a221fc1..c8a033d 100644 --- a/docs/realtime.md +++ b/docs/realtime.md @@ -7,6 +7,6 @@ **Response:** full-duplex Realtime JSON text and native binary/control frames. Event payloads, transcription events, errors and close behavior are relayed; no Responses event conversion is applied. The configured timeout is an idle timeout. -The upstream is `https://api.openai.com/v1/realtime`. Use `X-Codex-Gateway-Authorization` for the gateway key and `Authorization` for an API key when starting a direct session. Codex 0.160.0 requires an API key for its direct Realtime connection. Its existing WebRTC-call sideband explicitly supports the ChatGPT authorization used to create that call; ordinary gateway Bearer mode reuses the gateway subscription for this case. Subscription access to arbitrary new public Realtime sessions is not established by the source. +The upstream is `https://api.openai.com/v1/realtime`. Use `X-Codex-Gateway-Authorization` for the gateway key and `Authorization` for an API key when starting a direct session. Codex 0.162.1 requires an API key for its direct Realtime connection. Its existing WebRTC-call sideband explicitly supports the ChatGPT authorization used to create that call; ordinary gateway Bearer mode reuses the gateway subscription for this case. Subscription access to arbitrary new public Realtime sessions is not established by the source. [Pinned sideband authorization](https://github.com/openai/codex/blob/be2951ea34f0d295ed0becf97079f92fa5f6950e/codex-rs/core/src/client.rs#L412) · [Pinned direct-session gate](https://github.com/openai/codex/blob/be2951ea34f0d295ed0becf97079f92fa5f6950e/codex-rs/core/src/realtime_conversation.rs#L1773) · [Official WebSocket guide](https://developers.openai.com/api/docs/guides/voice-websockets?api=realtime) diff --git a/docs/responses.md b/docs/responses.md index 684e063..5bdf4ee 100644 --- a/docs/responses.md +++ b/docs/responses.md @@ -34,7 +34,7 @@ Known reasoning efforts are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `reasoning.generate_summary` is accepted as a deprecated alias of `summary`. Explicit `reasoning.context` values are preserved; `auto` and `current_turn` select regular Responses because Lite requires `all_turns`. Omitted/null context retains the Lite default of `all_turns` when the request uses Lite (and is omitted for regular requests). Requests containing hosted `web_search` always use regular Responses, where the hosted tool can execute. See the [SDK compatibility suite and parameter audit](sdk-compatibility.md). -Message inputs accept `role` (`user`, `assistant`, `system`, `developer`) and string or Codex content arrays. Public `system` messages are forwarded as `developer` messages because Codex rejects the `system` input role. Their content and position are preserved, and explicit top-level `instructions` remain unchanged. Text and image content, reasoning, function/custom calls and their outputs, web-search calls and Codex compaction items are supported. Functions/custom tools run in the caller, which submits their outputs in the next request. +Message inputs accept `role` (`user`, `assistant`, `system`, `developer`) and string or Codex content arrays. Public `system` messages are forwarded as `developer` messages because Codex rejects the `system` input role. Their content and position are preserved, and explicit top-level `instructions` retain their content and are prepended as a classified developer message, matching Codex 0.162.1 (for both standard and lite requests). Text and image content, reasoning, function/custom calls and their outputs, web-search calls and Codex compaction items are supported. Functions/custom tools run in the caller, which submits their outputs in the next request. HTTP continuation requires complete input history. `previous_response_id`, `background`, temperature/sampling controls, output-token limits and other unlisted top-level fields are not implemented here. diff --git a/docs/upgrade-0.162.1-validation.md b/docs/upgrade-0.162.1-validation.md new file mode 100644 index 0000000..67232f3 --- /dev/null +++ b/docs/upgrade-0.162.1-validation.md @@ -0,0 +1,51 @@ +# Codex 0.162.1 upgrade validation + +- Base: `main` / `8ae930e5ba3052433d041ca879e0aa96b6b0d503` (Codex 0.160.0). +- Upstream stable: `rust-v0.162.1`, commit `092d3acd6bec3e3a14bdc7e7a2810ab628ab759d`. +- Independent worktree and branch; incorporates the complete compatibility changes from PR #13 relative to main. Existing PRs are untouched. +- PR #13's Rust/client compatibility, image and publish checks were all successful when inspected. +- Updated all current Codex dependency pins, Cargo.lock packages, release asset checksums, runtime constants, vendored crate version, and documentation. Fixed the stale revision in the health example. +- Upstream codex-api sources are identical between 0.162.0 and 0.162.1; the full vendored refresh and raw-transport patch relative to main are included. Historical API inventory references intentionally remain 0.155.1. + +## Local validation + +Rust 1.95.0, CARGO_BUILD_JOBS=2, isolated target directory. All commands below completed successfully: + +```text +cargo fmt --all -- --check +cargo check --workspace --all-targets --locked +cargo clippy --workspace --all-targets --locked -- -D warnings +cargo setup +cargo test --workspace --locked +cargo test --locked --test gateway actual_ -- --ignored +cargo test --locked --test source_audit -- --ignored +``` + +Workspace: 287 passed, 0 failed, 12 ignored. Explicit official-client fixture compatibility: 5 passed. Explicit source audit: 1 passed. + +Source audit verified 66 pinned Codex git packages, 980 shared registry packages, 42 unchanged source files and 5 unchanged upstream integration-test files. The six intentional raw-transport source modifications exactly match the checked-in patch. Release binaries and companions passed SHA-256 and CLI version checks. + +## Limitations and environment recovery + +The default mise cargo shim initially failed before executing checks; using the existing `/home/coder/.cargo/bin` toolchain resolved this without system installation or global configuration changes. Device relay interruptions did not stop the validation process; results were recovered from logs. Credential-dependent live endpoint tests and container build/smoke were not run locally. Open this PR as Draft pending remote container CI and review; fixture compatibility is not a claim of live service access. + +## Evidence + +Full logs remain in `artifacts/maintenance-0.162.1/` in the isolated worktree. Exit records and log SHA-256 digests: + +```text +fmt exit=0 2026-10-09T22:03:21Z +check exit=0 2026-10-09T22:09:52Z +clippy exit=0 2026-10-09T22:10:26Z +setup exit=0 2026-10-09T22:10:33Z +workspace exit=0 2026-10-09T22:18:34Z +compatibility exit=0 2026-10-09T22:21:33Z +source-audit exit=0 2026-10-09T22:21:39Z +701eda61a442e6dbb1e244baa9e633501f86b8067c287009d8717e853844f3a1 check.log +01b0e1ae2c4b7bbe0a5f0f433ca46777d9311ec761f15c3f6cbbe62d8907a129 clippy.log +5a117d6255d80264e4d1aef832b09555dab1b8e941e6a6800ecbd53c7103b6a7 compatibility.log +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 fmt.log +07ba89728da8d2e1c66b4a69b324964a2402bf927f831297d81eb9caf59b23a3 setup.log +d8a19c00651429395aa23d082bf50679e680f99acbdeb39bec4ae3a46234e4d0 source-audit.log +637c2ab4da69fd4244cfc3a188e766df72c01961c3c89b70761409f46a2ac1d5 workspace.log +``` diff --git a/src/lib.rs b/src/lib.rs index b7cd546..acbe5ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,5 +18,5 @@ pub mod transfers; pub mod transport; pub mod websocket; -pub const CODEX_RELEASE: &str = "0.160.0"; -pub const CODEX_REV: &str = "a956835d020762cb2b570053af06f643a11c0ecc"; +pub const CODEX_RELEASE: &str = "0.162.1"; +pub const CODEX_REV: &str = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d"; diff --git a/src/lite_instructions.rs b/src/lite_instructions.rs index 8e8e547..306c2f7 100644 --- a/src/lite_instructions.rs +++ b/src/lite_instructions.rs @@ -1,4 +1,4 @@ -// Copied from Codex 0.160.0 core/context/base_instructions.rs; Apache-2.0. +// Copied from Codex 0.162.1 core/context/base_instructions.rs; Apache-2.0. use codex_context_fragments::ContextualUserFragment; use codex_protocol::models::ContentItemKind; @@ -14,10 +14,6 @@ impl ContextualUserFragment for BaseInstructionsFragment { "developer" } - fn requires_separate_message(&self) -> bool { - true - } - fn markers(&self) -> (&'static str, &'static str) { Self::type_markers() } diff --git a/src/request.rs b/src/request.rs index 8906df9..a78e694 100644 --- a/src/request.rs +++ b/src/request.rs @@ -215,41 +215,31 @@ impl CreateResponse { "service_tier is not supported by this Codex model", )); } - let mut instructions = self.instructions.unwrap_or_else(|| { + let instructions = self.instructions.unwrap_or_else(|| { model .model_messages .as_ref() .and_then(|v| v.instructions_template.clone()) .unwrap_or_else(|| codex_models_manager::model_info::BASE_INSTRUCTIONS.to_owned()) }); + let namespace = uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_OID, session_id.as_bytes()); + let mut prefix = Vec::new(); let tools = if lite { - let tools = codex_tools::create_tools_json_for_responses_lite(&tool_specs) - .map_err(GatewayError::internal)?; - let namespace = uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_OID, session_id.as_bytes()); - let mut prefix = vec![ResponseItem::AdditionalTools { - id: Some(codex_protocol::ResponseItemId::with_suffix( - "at", - uuid::Uuid::new_v5( - &namespace, - &serde_json::to_vec(&tools).map_err(GatewayError::internal)?, - ), - )), - role: "developer".into(), - tools, - }]; - if !instructions.is_empty() { - use codex_context_fragments::ContextualUserFragment; - let mut item = ContextualUserFragment::into( - crate::lite_instructions::BaseInstructionsFragment(instructions.clone()), - ); - item.set_id(Some(codex_protocol::ResponseItemId::with_suffix( - "msg", - uuid::Uuid::new_v5(&namespace, instructions.as_bytes()), - ))); - prefix.push(item); + if !tool_specs.is_empty() { + let tools = codex_tools::create_tools_json_for_responses_lite(&tool_specs) + .map_err(GatewayError::internal)?; + prefix.push(ResponseItem::AdditionalTools { + id: Some(codex_protocol::ResponseItemId::with_suffix( + "at", + uuid::Uuid::new_v5( + &namespace, + &serde_json::to_vec(&tools).map_err(GatewayError::internal)?, + ), + )), + role: "developer".into(), + tools, + }); } - input.splice(0..0, prefix); - instructions.clear(); None } else { Some( @@ -258,13 +248,24 @@ impl CreateResponse { .into(), ) }; + if !instructions.is_empty() { + use codex_context_fragments::ContextualUserFragment; + let mut item = ContextualUserFragment::into( + crate::lite_instructions::BaseInstructionsFragment(instructions.clone()), + ); + item.set_id(Some(codex_protocol::ResponseItemId::with_suffix( + "msg", + uuid::Uuid::new_v5(&namespace, instructions.as_bytes()), + ))); + prefix.push(item); + } + input.splice(0..0, prefix); let mut include = self.include.unwrap_or_default(); if !include.iter().any(|v| v == "reasoning.encrypted_content") { include.push("reasoning.encrypted_content".into()); } Ok(ResponsesApiRequest { model: model.slug.clone(), - instructions, input, tools, tool_choice: "auto".into(), diff --git a/tests/cases/response_types.rs b/tests/cases/response_types.rs index 2ba29c2..beec489 100644 --- a/tests/cases/response_types.rs +++ b/tests/cases/response_types.rs @@ -54,9 +54,9 @@ async fn actual_openai_sdk_response_types() { let row = scenario(&format!("context-{context}-stream-{stream}")); assert_eq!(row.1["reasoning"]["context"], context); let expected_lite = is_lite && context == "all_turns"; - assert_eq!( - row.1["input"][0]["type"] == "additional_tools", - expected_lite + assert!( + row.1["input"][0]["type"] != "additional_tools", + "requests without tools omit additional_tools in Codex 0.162.1" ); assert_eq!( row.0.contains_key("x-openai-internal-codex-responses-lite"), diff --git a/tests/cases/tools.rs b/tests/cases/tools.rs index 68d2e54..50c36a4 100644 --- a/tests/cases/tools.rs +++ b/tests/cases/tools.rs @@ -282,6 +282,7 @@ http_headers = {{ "x-openai-actor-authorization" = "gateway" }} let output = tokio::time::timeout( Duration::from_secs(60), tokio::process::Command::new(binary) + .stdin(std::process::Stdio::null()) .env("CODEX_HOME", home.path()) .env("CODEX_GATEWAY_API_KEY", "client-key") .env_remove("OPENAI_API_KEY") diff --git a/tests/gateway.rs b/tests/gateway.rs index de5fe1b..118e529 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -388,10 +388,17 @@ async fn stream_preserves_full_events_and_uses_codex_request_and_headers() { assert_eq!(body["tool_choice"], "auto"); assert_eq!(body["include"], json!(["reasoning.encrypted_content"])); assert_eq!( - body["input"], + json!(&body["input"].as_array().unwrap()[1..]), json!([{"type":"message","role":"user","content":[{"type":"input_text","text":"hello"}]}]) ); - assert!(!body["instructions"].as_str().unwrap().is_empty()); + assert!(body.get("instructions").is_none()); + assert_eq!(body["input"][0]["role"], "developer"); + assert!( + !body["input"][0]["content"][0]["text"] + .as_str() + .unwrap() + .is_empty() + ); } #[tokio::test] @@ -422,8 +429,12 @@ async fn system_messages_are_normalized_for_codex_upstream() { let received = h.fake.received.lock().unwrap(); assert_eq!(received.len(), 2); for (_, body) in received.iter() { - assert_eq!(body["instructions"], "Keep explicit instructions."); - let input = body["input"].as_array().unwrap(); + assert!(body.get("instructions").is_none()); + assert_eq!( + body["input"][0]["content"][0]["text"], + "Keep explicit instructions." + ); + let input = &body["input"].as_array().unwrap()[1..]; assert_eq!(input.len(), 6); assert_eq!( input @@ -481,8 +492,8 @@ async fn function_arguments_stream_and_tool_results_are_not_executed_or_lost() { assert_eq!(parse_sse(&response.text().await.unwrap()), expected); let received = h.fake.received.lock().unwrap(); assert_eq!(received.len(), 1); - assert_eq!(received[0].1["input"][2]["call_id"], "call_1"); - assert_eq!(received[0].1["input"][2]["output"], "sunny"); + assert_eq!(received[0].1["input"][3]["call_id"], "call_1"); + assert_eq!(received[0].1["input"][3]["output"], "sunny"); } #[tokio::test] @@ -727,7 +738,7 @@ async fn subscription_uses_codex_account_auth_and_zstd_without_reading_real_cred assert_eq!(received[0].0["chatgpt-account-id"], "account_id"); assert_eq!(received[0].0["content-encoding"], "zstd"); assert_eq!( - received[0].1["input"][0]["content"][0]["text"], + received[0].1["input"][1]["content"][0]["text"], "subscription request" ); } @@ -1364,6 +1375,7 @@ supports_websockets = {} let output = tokio::time::timeout( Duration::from_secs(60), tokio::process::Command::new(&binary) + .stdin(std::process::Stdio::null()) .env("CODEX_HOME", home.path()) .env("CODEX_GATEWAY_API_KEY", "client-key") .env_remove("OPENAI_API_KEY") @@ -1436,7 +1448,16 @@ async fn public_ws_accepts_sdk_input_and_builds_lite_request() { } let received = h.fake.received.lock().unwrap(); let body = &received[0].1; - assert_eq!(body["input"][0]["type"], "additional_tools"); + assert_eq!(body["input"][0]["type"], "message"); + assert_eq!(body["input"][0]["role"], "developer"); + assert_eq!(body["input"][0]["content"][0]["text"], "brief"); + assert!( + body["input"] + .as_array() + .unwrap() + .iter() + .all(|item| item["type"] != "additional_tools") + ); assert_eq!(body["reasoning"]["context"], "all_turns"); assert_eq!(body["generate"], false); assert_eq!(body["stream_id"], "lane-1"); @@ -1548,6 +1569,7 @@ supports_websockets = {} let output = tokio::time::timeout( Duration::from_secs(60), tokio::process::Command::new(&binary) + .stdin(std::process::Stdio::null()) .env("CODEX_HOME", home.path()) .env("CODEX_GATEWAY_API_KEY", "client-key") .env_remove("OPENAI_API_KEY") diff --git a/vendor/codex-api/Cargo.toml b/vendor/codex-api/Cargo.toml index 134d82b..ac710cf 100644 --- a/vendor/codex-api/Cargo.toml +++ b/vendor/codex-api/Cargo.toml @@ -1,7 +1,7 @@ -# Vendored from Codex rust-v0.160.0; workspace dependencies expanded. +# Vendored from Codex rust-v0.162.1; workspace dependencies expanded. [package] name = "codex-api" -version = "0.160.0" +version = "0.162.1" edition = "2024" license = "Apache-2.0" @@ -10,11 +10,11 @@ async-channel = { version = "2.3.1" } base64 = { version = "0.22.1" } bytes = { version = "1.10.1" } chrono = { version = "0.4.43" } -codex-client = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-http-client = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-protocol = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-utils-rustls-provider = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } -codex-websocket-client = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-client = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-http-client = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-protocol = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-utils-rustls-provider = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } +codex-websocket-client = { git = "https://github.com/openai/codex.git", rev = "092d3acd6bec3e3a14bdc7e7a2810ab628ab759d" } futures = { version = "0.3", default-features = false } http = { version = "1.3.1" } schemars = { version = "0.8.22" } diff --git a/vendor/codex-api/src/api_bridge_tests.rs b/vendor/codex-api/src/api_bridge_tests.rs index 56c4838..c5504e2 100644 --- a/vendor/codex-api/src/api_bridge_tests.rs +++ b/vendor/codex-api/src/api_bridge_tests.rs @@ -74,6 +74,56 @@ async fn map_api_error_preserves_retry_delay() { } } +/// Mapping a new error category preserves elapsed advice without making hard failures retryable. +#[tokio::test(start_paused = true)] +async fn http_retry_deadline_survives_mapping_and_respects_terminal_errors() { + use std::time::Duration; + + let advice = RetryAfter::from_delay(Duration::from_secs(10)).expect("retry advice"); + tokio::time::advance(Duration::from_secs(4)).await; + for (status, code, retryable) in [ + (503, "server_is_overloaded", true), + (429, "rate_limit_exceeded", true), + (429, "insufficient_quota", false), + (429, "usage_limit_reached", false), + (400, "cyber_policy", false), + ] { + let error = map_api_error(ApiError::Transport(TransportError::Http { + status: http::StatusCode::from_u16(status).unwrap(), + url: None, + headers: None, + body: Some(serde_json::json!({"error": {"type": code, "code": code, "message": "sensitive upstream detail"}}).to_string()), + retry_after: Some(advice), + })); + assert_eq!( + (error.retry_after(), error.server_retry_delay()), + (Some(advice), Some(Duration::from_secs(6))), + "{code}", + ); + assert_eq!( + error.retry_delay(/*retry_count*/ 1), + retryable.then_some(Duration::from_secs(6)), + "{code}", + ); + if status == 429 && retryable { + assert_eq!( + ( + error.to_codex_protocol_error(), + error.http_status_code_value(), + error.to_string() + ), + ( + CodexErrorInfo::ResponseTooManyFailedAttempts { + http_status_code: Some(429), + }, + Some(429), + "exceeded retry limit, last status: 429 Too Many Requests".to_string(), + ), + ); + } + } +} + #[test] fn map_api_error_distinguishes_capacity_from_slow_down() { for (code, expected, retryable) in [ @@ -337,6 +387,7 @@ fn map_api_error_preserves_misalignment_details_from_403_body() { "code": "misalignment_policy_violation", "misalignment": { "error_type": "unauthorized_data_transfer", + "review_target": " RB/opaque== ", "detailed_explanation": "The agent attempted an external transfer.", "steer": { "message": "Do not transfer the user's files." } } @@ -363,6 +414,7 @@ fn map_api_error_preserves_misalignment_details_from_403_body() { misalignment, &Some(MisalignmentErrorDetails { error_type: Some("unauthorized_data_transfer".to_string()), + review_target: Some(" RB/opaque== ".to_string()), detailed_explanation: Some("The agent attempted an external transfer.".to_string()), steer: Some(codex_protocol::protocol::MisalignmentSteer { message: "Do not transfer the user's files.".to_string(), @@ -382,6 +434,7 @@ fn map_api_error_preserves_misalignment_details_from_wrapped_websocket_error() { "code": "misalignment_policy_violation", "misalignment": { "error_type": "future_safety_category", + "review_target": " RB/opaque== ", "detailed_explanation": "The agent attempted an external transfer.", "steer": { "message": "Do not transfer the user's files." } } @@ -411,6 +464,7 @@ fn map_api_error_preserves_misalignment_details_from_wrapped_websocket_error() { misalignment, &Some(MisalignmentErrorDetails { error_type: Some("future_safety_category".to_string()), + review_target: Some(" RB/opaque== ".to_string()), detailed_explanation: Some("The agent attempted an external transfer.".to_string()), steer: Some(codex_protocol::protocol::MisalignmentSteer { message: "Do not transfer the user's files.".to_string(), diff --git a/vendor/codex-api/src/common.rs b/vendor/codex-api/src/common.rs index 8d0d929..42c9fec 100644 --- a/vendor/codex-api/src/common.rs +++ b/vendor/codex-api/src/common.rs @@ -277,9 +277,12 @@ impl Serialize for ResponsesApiTools { #[derive(Debug, Serialize, Clone, PartialEq)] pub struct ResponsesApiRequest { + // Keep routing fields first: serde serializes struct fields in declaration order, and + // gateways may inspect request bodies incrementally before potentially multi-megabyte input. pub model: String, - #[serde(skip_serializing_if = "String::is_empty")] - pub instructions: String, + pub stream: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub service_tier: Option, pub input: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub tools: Option, @@ -287,13 +290,10 @@ pub struct ResponsesApiRequest { pub parallel_tool_calls: bool, pub reasoning: Option, pub store: bool, - pub stream: bool, #[serde(skip_serializing_if = "Option::is_none")] pub stream_options: Option, pub include: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub service_tier: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option, #[serde(skip_serializing_if = "Option::is_none")] pub text: Option, @@ -307,7 +307,6 @@ impl<'a> From<&'a ResponsesApiRequest> for ResponseCreateWsRequest<'a> { fn from(request: &'a ResponsesApiRequest) -> Self { Self { model: &request.model, - instructions: &request.instructions, previous_response_id: None, input: &request.input, tools: request.tools.as_ref().map(ResponsesApiTools::as_raw_value), @@ -330,9 +329,12 @@ impl<'a> From<&'a ResponsesApiRequest> for ResponseCreateWsRequest<'a> { #[derive(Debug, Serialize)] pub struct ResponseCreateWsRequest<'a> { + // Keep routing fields first: serde serializes struct fields in declaration order, and + // gateways may inspect request bodies incrementally before potentially multi-megabyte input. pub model: &'a str, - #[serde(skip_serializing_if = "str::is_empty")] - pub instructions: &'a str, + pub stream: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub service_tier: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] pub previous_response_id: Option, pub input: &'a [ResponseItem], @@ -342,13 +344,10 @@ pub struct ResponseCreateWsRequest<'a> { pub parallel_tool_calls: bool, pub reasoning: Option<&'a Reasoning>, pub store: bool, - pub stream: bool, #[serde(skip_serializing_if = "Option::is_none")] pub stream_options: Option<&'a StreamOptions>, pub include: &'a [String], #[serde(skip_serializing_if = "Option::is_none")] - pub service_tier: Option<&'a str>, - #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] pub text: Option<&'a TextControls>, diff --git a/vendor/codex-api/src/endpoint/realtime_websocket/methods.rs b/vendor/codex-api/src/endpoint/realtime_websocket/methods.rs index 1e766d2..ae85930 100644 --- a/vendor/codex-api/src/endpoint/realtime_websocket/methods.rs +++ b/vendor/codex-api/src/endpoint/realtime_websocket/methods.rs @@ -42,6 +42,7 @@ use std::collections::VecDeque; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; +use std::time::Duration; use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::oneshot; @@ -943,15 +944,28 @@ impl RealtimeWebsocketClient { default_headers: HeaderMap, transcript_state: RealtimeTranscriptState, ) -> Result { - self.connect_sideband( - config, - call_id, - extra_headers, - default_headers, - RealtimeSessionInitialization::ExistingCall, - transcript_state, - ) - .await + // Instant Connect returns a call ID before ICE activates the call. Its sideband + // can briefly return 404 while the client connects media in parallel. + let mut activation_delays = [100, 300, 800].into_iter(); + loop { + let result = self + .connect_sideband( + config.clone(), + call_id, + extra_headers.clone(), + default_headers.clone(), + RealtimeSessionInitialization::ExistingCall, + transcript_state.clone(), + ) + .await; + if matches!(&result, Err(ApiError::Api { status, .. }) if *status == StatusCode::NOT_FOUND) + && let Some(delay_ms) = activation_delays.next() + { + sleep(Duration::from_millis(delay_ms)).await; + continue; + } + return result; + } } async fn connect_sideband( diff --git a/vendor/codex-api/src/endpoint/responses_websocket.rs b/vendor/codex-api/src/endpoint/responses_websocket.rs index 2580000..2e40160 100644 --- a/vendor/codex-api/src/endpoint/responses_websocket.rs +++ b/vendor/codex-api/src/endpoint/responses_websocket.rs @@ -14,6 +14,7 @@ use crate::sse::process_responses_event; use crate::telemetry::WebsocketTelemetry; use codex_client::TransportError; use codex_http_client::HttpClientFactory; +use codex_http_client::RetryAfter; use codex_websocket_client::WebSocketConnection; use codex_websocket_client::WebSocketConnector; use futures::FutureExt; @@ -348,6 +349,8 @@ impl ResponsesWebsocketConnection { } } +mod connector; + /// Client for connecting to the Responses WebSocket endpoint for one provider. pub struct ResponsesWebsocketClient { provider: Provider, @@ -384,12 +387,6 @@ impl ResponsesWebsocketClient { Self { provider, auth } } - #[instrument( - name = "responses_websocket.connect", - level = "info", - skip_all, - fields(transport = "responses_websocket", api.path = "/responses") - )] pub async fn connect( &self, http_client_factory: &HttpClientFactory, @@ -398,24 +395,16 @@ impl ResponsesWebsocketClient { turn_state: Option>>, telemetry: Option>, ) -> Result { - let ws_url = self - .provider - .websocket_url_for_path("/responses") - .map_err(|err| ApiError::Stream(format!("failed to build websocket URL: {err}")))?; - - let mut headers = - merge_request_headers(&self.provider.headers, extra_headers, default_headers); - self.auth.add_auth_headers(&mut headers); - - let (stream, _status, server_reasoning_included, server_model) = - connect_websocket(ws_url, headers, http_client_factory, turn_state.clone()).await?; - Ok(ResponsesWebsocketConnection::new( - stream, - self.provider.stream_idle_timeout, - server_reasoning_included, - server_model, + let connector = WebSocketConnector::new(http_client_factory) + .map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?; + self.connect_with_connector( + &connector, + extra_headers, + default_headers, + turn_state, telemetry, - )) + ) + .await } /// Gateway extension: expose the original handshake without the lossy event adapter. @@ -459,7 +448,9 @@ impl ResponsesWebsocketClient { let mut headers = merge_request_headers(&self.provider.headers, extra_headers, default_headers); self.auth.add_auth_headers(&mut headers); - connect_websocket_raw(url, headers, http_client_factory).await + let connector = WebSocketConnector::new(http_client_factory) + .map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?; + connect_websocket_raw(url, headers, &connector).await } /// Opens a WebSocket connection long enough to validate the upgrade response. @@ -485,10 +476,12 @@ impl ResponsesWebsocketClient { merge_request_headers(&self.provider.headers, extra_headers, default_headers); self.auth.add_auth_headers(&mut headers); + let connector = WebSocketConnector::new(http_client_factory) + .map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?; let (mut stream, status, reasoning_included, server_model) = connect_websocket( ws_url.clone(), headers, - http_client_factory, + &connector, /*turn_state*/ None, ) .await?; @@ -542,10 +535,10 @@ fn merge_request_headers( async fn connect_websocket( url: Url, headers: HeaderMap, - http_client_factory: &HttpClientFactory, + connector: &WebSocketConnector, turn_state: Option>>, ) -> Result<(WsStream, StatusCode, bool, Option), ApiError> { - let (stream, response) = connect_websocket_raw(url, headers, http_client_factory).await?; + let (stream, response) = connect_websocket_raw(url, headers, connector).await?; let reasoning_included = response.headers().contains_key(X_REASONING_INCLUDED_HEADER); let server_model = response @@ -572,7 +565,7 @@ async fn connect_websocket( async fn connect_websocket_raw( url: Url, headers: HeaderMap, - http_client_factory: &HttpClientFactory, + connector: &WebSocketConnector, ) -> Result<(WebSocketConnection, http::Response>>), ApiError> { info!("connecting to websocket: {url}"); @@ -582,8 +575,6 @@ async fn connect_websocket_raw( .map_err(|err| ApiError::Stream(format!("failed to build websocket request: {err}")))?; request.headers_mut().extend(headers); - let connector = WebSocketConnector::new(http_client_factory) - .map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?; let response = connector.connect(request, websocket_config()).await; let (stream, response) = match response { @@ -624,6 +615,7 @@ fn map_ws_error(err: WsError, url: &Url) -> ApiError { WsError::Http(response) => { let status = response.status(); let headers = response.headers().clone(); + let retry_after = RetryAfter::from_headers(&headers); let body = response .body() .as_ref() @@ -633,7 +625,7 @@ fn map_ws_error(err: WsError, url: &Url) -> ApiError { url: Some(url.to_string()), headers: Some(headers), body, - retry_after: None, + retry_after, }) } WsError::ConnectionClosed | WsError::AlreadyClosed => { @@ -648,6 +640,8 @@ fn map_ws_error(err: WsError, url: &Url) -> ApiError { struct WrappedWebsocketError { code: Option, message: Option, + #[serde(default)] + headers: Option, } #[derive(Debug, Deserialize)] @@ -680,6 +674,16 @@ fn map_wrapped_websocket_error_event( headers, .. } = event; + let retry_after = [ + error + .as_ref() + .and_then(|error| error.headers.as_ref()) + .and_then(Value::as_object), + headers.as_ref(), + ] + .into_iter() + .flatten() + .find_map(|headers| RetryAfter::from_headers(&json_headers_to_http_headers(headers))); if let Some(error) = error.as_ref() && let Some(code) = error.code.as_deref() @@ -696,7 +700,7 @@ fn map_wrapped_websocket_error_event( .message .clone() .unwrap_or_else(|| fallback_message.to_string()), - retry_after: None, + retry_after, }); } @@ -710,7 +714,7 @@ fn map_wrapped_websocket_error_event( url: None, headers: headers.as_ref().map(json_headers_to_http_headers), body: Some(original_payload), - retry_after: None, + retry_after, })) } @@ -997,7 +1001,6 @@ mod tests { fn direct_serialization_preserves_websocket_request_payload() { let api_request = ResponsesApiRequest { model: "gpt-test".to_string(), - instructions: "Use the available tools.".to_string(), input: vec![ResponseItem::Message { id: Some(ResponseItemId::with_suffix("msg", "1")), role: "user".to_string(), @@ -1049,6 +1052,9 @@ mod tests { expected_payload["generate"] = json!(false); let request_text = serialize_websocket_request(&request).expect("serialize websocket request"); + assert!(request_text.starts_with( + r#"{"type":"response.create","model":"gpt-test","stream":true,"service_tier":"priority","previous_response_id":"resp-1","input":"# + )); let wire_payload = serde_json::from_str::(&request_text).expect("parse websocket request"); @@ -1113,6 +1119,35 @@ mod tests { assert!(body.contains("The usage limit has been reached")); } + /// Websocket error advice uses the shared HTTP value validation and duplicate handling. + #[tokio::test(start_paused = true)] + async fn wrapped_websocket_retry_after_uses_http_header_validation() { + for (error_headers, expected_seconds) in [ + (json!("invalid"), 12), + (json!({"retry-after": "\n5\n"}), 12), + (json!({"retry-after": "\t5\t"}), 5), + (json!({"Retry-After": "5", "retry-after": "30"}), 30), + ] { + let payload = json!({ + "type": "error", + "status": 429, + "error": {"code": "rate_limit_exceeded", "headers": error_headers}, + "headers": {"retry-after": "12"} + }) + .to_string(); + let wrapped = parse_wrapped_websocket_error_event(&payload).unwrap(); + let Some(ApiError::Transport(TransportError::Http { retry_after, .. })) = + map_wrapped_websocket_error_event(wrapped, payload) + else { + panic!("expected a websocket HTTP error"); + }; + assert_eq!( + retry_after, + RetryAfter::from_delay(Duration::from_secs(expected_seconds)) + ); + } + } + #[test] fn wrapped_websocket_usage_limit_preserves_optional_window() { for (window, expected) in [(Some(10080), Some(10080)), (None, None)] { diff --git a/vendor/codex-api/src/endpoint/responses_websocket/connector.rs b/vendor/codex-api/src/endpoint/responses_websocket/connector.rs new file mode 100644 index 0000000..e305ca3 --- /dev/null +++ b/vendor/codex-api/src/endpoint/responses_websocket/connector.rs @@ -0,0 +1,49 @@ +//! Establishes Responses sockets with caller-owned TLS and proxy configuration. +//! Connector reuse never caches authentication or bypasses per-connection destination policy. + +use super::ResponsesWebsocketClient; +use super::ResponsesWebsocketConnection; +use super::connect_websocket; +use super::merge_request_headers; +use crate::ApiError; +use crate::WebsocketTelemetry; +use codex_websocket_client::WebSocketConnector; +use http::HeaderMap; +use std::sync::Arc; +use std::sync::OnceLock; + +impl ResponsesWebsocketClient { + /// Opens a socket using the caller's reusable connector and this client's current auth. + /// The caller owns the lifetime of the connector's TLS configuration snapshot. + #[tracing::instrument( + name = "responses_websocket.connect", + level = "info", + skip_all, + fields(transport = "responses_websocket", api.path = "/responses") + )] + pub async fn connect_with_connector( + &self, + connector: &WebSocketConnector, + extra_headers: HeaderMap, + default_headers: HeaderMap, + turn_state: Option>>, + telemetry: Option>, + ) -> Result { + let ws_url = self + .provider + .websocket_url_for_path("/responses") + .map_err(|err| ApiError::Stream(format!("failed to build websocket URL: {err}")))?; + let mut headers = + merge_request_headers(&self.provider.headers, extra_headers, default_headers); + self.auth.add_auth_headers(&mut headers); + let (stream, _status, server_reasoning_included, server_model) = + connect_websocket(ws_url, headers, connector, turn_state).await?; + Ok(ResponsesWebsocketConnection::new( + stream, + self.provider.stream_idle_timeout, + server_reasoning_included, + server_model, + telemetry, + )) + } +} diff --git a/vendor/codex-api/src/sse/responses.rs b/vendor/codex-api/src/sse/responses.rs index 6b2a7ed..78885f4 100644 --- a/vendor/codex-api/src/sse/responses.rs +++ b/vendor/codex-api/src/sse/responses.rs @@ -649,6 +649,10 @@ async fn process_sse_with_treatment( } } +#[cfg(test)] +#[path = "responses_error_tests.rs"] +mod error_tests; + #[cfg(test)] mod tests { use super::*; @@ -730,6 +734,46 @@ mod tests { Duration::from_millis(1000) } + #[tokio::test] + async fn partial_answer_phase_survives_item_start_and_completion() { + let item = json!({ + "id": "msg_partial", + "type": "message", + "role": "assistant", + "content": [], + "phase": "partial_answer" + }); + let added = json!({"type": "response.output_item.added", "item": item}); + let delta = json!({"type": "response.output_text.delta", "delta": "First result."}); + let mut done_item = item; + done_item["content"] = json!([{"type": "output_text", "text": "First result."}]); + let done = json!({"type": "response.output_item.done", "item": done_item}); + let completed = json!({ + "type": "response.completed", + "response": {"id": "resp1", "end_turn": false} + }); + let stream = [added, delta, done, completed] + .into_iter() + .map(|event| format!("data: {event}\n\n")) + .collect::(); + + let events = collect_events(&[stream.as_bytes()]).await; + assert_matches!(events.as_slice(), [ + Ok(ResponseEvent::OutputItemAdded(ResponseItem::Message { + phase: Some(MessagePhase::PartialAnswer), content: start_content, .. + })), + Ok(ResponseEvent::OutputTextDelta(text)), + Ok(ResponseEvent::OutputItemDone(ResponseItem::Message { + phase: Some(MessagePhase::PartialAnswer), content: done_content, .. + })), + Ok(ResponseEvent::Completed { end_turn: Some(false), .. }), + ] if start_content.is_empty() + && text == "First result." + && done_content == &vec![codex_protocol::models::ContentItem::OutputText { + text: "First result.".to_string() + }]); + } + #[tokio::test] async fn parses_items_and_completed() { let item1 = json!({ @@ -1247,6 +1291,7 @@ mod tests { misalignment, &Some(MisalignmentErrorDetails { error_type: Some("future_safety_category".to_string()), + review_target: None, detailed_explanation: Some( "The agent attempted an external transfer.".to_string() ), diff --git a/vendor/codex-api/src/sse/responses_error.rs b/vendor/codex-api/src/sse/responses_error.rs index 872769c..890f45a 100644 --- a/vendor/codex-api/src/sse/responses_error.rs +++ b/vendor/codex-api/src/sse/responses_error.rs @@ -1,8 +1,9 @@ -//! Classifies failed Responses events and their rate-limit retry delays. +//! Classifies failed Responses events and their retry advice. //! Only rate-limit codes derive retry delays from plaintext messages. use crate::error::ApiError; use crate::error::parse_flex_unavailable; +use crate::responses_headers::json_headers_to_http_headers; use codex_http_client::RetryAfter; use codex_protocol::protocol::MisalignmentErrorDetails; use serde::Deserialize; @@ -23,19 +24,24 @@ struct Error { } pub(super) fn parse_failed_response(response: Option) -> ApiError { - if let Some(error) = response - .as_ref() - .and_then(|response| response.get("error")) - .and_then(parse_flex_unavailable) - { + let error = response.as_ref().and_then(|response| response.get("error")); + let retry_after_header = error + .and_then(|error| error.get("headers")) + .and_then(Value::as_object) + .and_then(|headers| RetryAfter::from_headers(&json_headers_to_http_headers(headers))); + if let Some(error) = error.and_then(parse_flex_unavailable) { return error; } - let Some(error) = response - .as_ref() - .and_then(|response| response.get("error")) - .and_then(|error| serde_json::from_value::(error.clone()).ok()) + let Some(error) = error.and_then(|error| serde_json::from_value::(error.clone()).ok()) else { - return ApiError::Stream("response.failed event received".into()); + let message = "response.failed event received".to_string(); + return match retry_after_header { + Some(_) => ApiError::Retryable { + message, + retry_after: retry_after_header, + }, + None => ApiError::Stream(message), + }; }; match error.code.as_deref() { @@ -78,9 +84,12 @@ pub(super) fn parse_failed_response(response: Option) -> ApiError { .message .unwrap_or_else(|| "Invalid request.".to_string()), }, - Some("server_is_overloaded") => ApiError::ServerOverloaded { retry_after: None }, + Some("server_is_overloaded") => ApiError::ServerOverloaded { + retry_after: retry_after_header, + }, Some("rate_limit_exceeded" | "slow_down") => { - let retry_after = try_parse_retry_delay(&error).and_then(RetryAfter::from_delay); + let retry_after = retry_after_header + .or_else(|| try_parse_retry_delay(&error).and_then(RetryAfter::from_delay)); ApiError::RateLimitExceeded { message: error.message.unwrap_or_default(), retry_after, @@ -88,7 +97,7 @@ pub(super) fn parse_failed_response(response: Option) -> ApiError { } _ => ApiError::Retryable { message: error.message.unwrap_or_default(), - retry_after: None, + retry_after: retry_after_header, }, } } @@ -106,7 +115,7 @@ fn try_parse_retry_delay(err: &Error) -> Option { let unit = unit.as_str().to_ascii_lowercase(); if unit == "s" || unit.starts_with("second") { - return Some(Duration::from_secs_f64(value)); + return Duration::try_from_secs_f64(value).ok(); } else if unit == "ms" { return Some(Duration::from_millis(value as u64)); } diff --git a/vendor/codex-api/src/sse/responses_error_tests.rs b/vendor/codex-api/src/sse/responses_error_tests.rs new file mode 100644 index 0000000..aeeb4ce --- /dev/null +++ b/vendor/codex-api/src/sse/responses_error_tests.rs @@ -0,0 +1,156 @@ +//! Regression coverage for retry advice carried by failed Responses events. + +use super::process_responses_event; +use super::spawn_response_stream; +use crate::api_bridge::map_api_error; +use crate::common::ResponseEvent; +use bytes::Bytes; +use codex_client::StreamResponse; +use codex_http_client::RetryAfter; +use codex_protocol::protocol::CodexErrorInfo; +use futures::StreamExt; +use futures::stream; +use http::HeaderMap; +use http::StatusCode; +use pretty_assertions::assert_eq; +use serde_json::json; +use std::time::Duration; + +/// Flex failures remain terminal even with retry headers and a later completed event. +#[tokio::test(start_paused = true)] +async fn flex_failure_with_retry_header_ends_stream_immediately() { + let error = json!({ + "code": "flex_unavailable", + "plan_type": 42, + "headers": {"retry-after": "300"} + }); + for failure in [ + json!({"type": "error", "error": error}), + json!({"type": "response.failed", "response": {"error": error}}), + ] { + let completed = json!({"type": "response.completed", "response": {"id": "later"}}); + let bytes = stream::iter([Ok(Bytes::from(format!( + "data: {failure}\n\ndata: {completed}\n\n" + )))]) + .chain(stream::pending()); + let mut stream = spawn_response_stream( + StreamResponse { + status: StatusCode::OK, + headers: HeaderMap::new(), + bytes: Box::pin(bytes), + }, + Duration::from_secs(60), + /*telemetry*/ None, + /*turn_state*/ None, + ); + let error = loop { + match stream.next().await { + Some(Ok(ResponseEvent::RateLimits(_))) => {} + Some(Err(error)) => break map_api_error(error), + event => panic!("expected a Flex error, got {event:?}"), + } + }; + assert_eq!( + ( + error.to_codex_protocol_error(), + error.retry_delay(/*retry_count*/ 1) + ), + (CodexErrorInfo::FlexUnavailable, None) + ); + assert!(stream.next().await.is_none()); + } +} + +/// Error headers beat rate-limit message advice; invalid ones fall back. +#[tokio::test(start_paused = true)] +async fn rate_limit_plaintext_loses_to_valid_error_header() { + for code in ["rate_limit_exceeded", "slow_down"] { + for (error_header, expected_seconds) in [ + (Some("2"), 2), + (Some("0"), 0), + (Some("Wed, 21 Oct 2015 07:28:00 GMT"), 0), + (Some("bad"), 35), + (None, 35), + ] { + let event = serde_json::from_value(json!({ + "type": "response.failed", + "response": { + "error": { + "code": code, + "message": "Try again in 35 seconds.", + "headers": {"Retry-After": error_header} + } + } + })) + .unwrap(); + let error = map_api_error(process_responses_event(event).unwrap_err().into_api_error()); + assert_eq!( + error.retry_after(), + RetryAfter::from_delay(Duration::from_secs(expected_seconds)) + ); + } + } +} + +/// Retry advice uses the same HTTP value validation as other event headers. +#[tokio::test(start_paused = true)] +async fn streamed_retry_after_uses_http_header_validation() { + for (error_headers, expected_seconds) in [ + (json!({"retry-after": "\n5\n"}), 12), + (json!({"retry-after": "\t5\t"}), 5), + (json!({"Retry-After": "5", "retry-after": "30"}), 30), + ] { + let event = serde_json::from_value(json!({ + "type": "response.failed", + "response": {"error": {"code": "rate_limit_exceeded", "message": "Try again in 12 seconds.", "headers": error_headers}} + })) + .unwrap(); + let error = map_api_error(process_responses_event(event).unwrap_err().into_api_error()); + assert_eq!( + error.retry_after(), + RetryAfter::from_delay(Duration::from_secs(expected_seconds)) + ); + } +} + +/// Event headers retain their deadlines for overload and generic failures. +#[tokio::test(start_paused = true)] +async fn retry_header_survives_other_failed_responses() { + for (code, expected_code) in [ + ("server_is_overloaded", CodexErrorInfo::ServerOverloaded), + ("unknown_failure", CodexErrorInfo::Other), + ] { + let event = serde_json::from_value(json!({ + "type": "response.failed", + "response": {"error": {"code": code, "headers": {"retry-after": "7"}}}, + })) + .unwrap(); + let error = map_api_error(process_responses_event(event).unwrap_err().into_api_error()); + assert_eq!( + ( + error.to_codex_protocol_error(), + error.retry_after(), + error.retry_delay(/*retry_count*/ 1), + ), + ( + expected_code, + RetryAfter::from_delay(Duration::from_secs(7)), + Some(Duration::from_secs(7)), + ) + ); + } +} + +/// An event header cannot make a quota or policy failure retryable. +#[tokio::test(start_paused = true)] +async fn retry_header_does_not_override_terminal_errors() { + for code in ["insufficient_quota", "cyber_policy", "bio_policy"] { + let event = serde_json::from_value(json!({ + "type": "response.failed", + "response": {"error": {"code": code, "headers": {"retry-after": "1"}}} + })) + .unwrap(); + let error = map_api_error(process_responses_event(event).unwrap_err().into_api_error()); + assert_eq!(error.retry_delay(/*retry_count*/ 1), None, "{code}"); + } +} diff --git a/vendor/codex-api/tests/clients.rs b/vendor/codex-api/tests/clients.rs index d2ac117..b17da45 100644 --- a/vendor/codex-api/tests/clients.rs +++ b/vendor/codex-api/tests/clients.rs @@ -339,7 +339,6 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> { let client = ResponsesClient::new(transport, provider("openai"), Arc::new(NoAuth)); let request = ResponsesApiRequest { model: "gpt-test".into(), - instructions: "Say hi".into(), input: vec![ResponseItem::Message { id: Some(ResponseItemId::with_suffix("msg", "1")), role: "user".into(), @@ -376,6 +375,7 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> { serde_json::from_slice(prepared.body.as_deref().expect("body should be JSON"))?; assert_eq!(body, expected); assert_eq!(body["input"][0]["id"], "msg_1"); + assert_eq!(body.get("service_tier"), None); assert_eq!( prepared.headers.get(http::header::CONTENT_TYPE), Some(&HeaderValue::from_static("application/json")) @@ -383,6 +383,62 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> { Ok(()) } +#[tokio::test] +async fn responses_client_stream_request_sends_routing_fields_ahead_of_large_input() -> Result<()> { + let state = RecordingState::default(); + let transport = RecordingTransport::new(state.clone()); + let client = ResponsesClient::new(transport, provider("openai"), Arc::new(NoAuth)); + // Exercise the customer gateway case where routing fields must be available without + // buffering a potentially multi-megabyte prompt first. + let large_input = "x".repeat(2 * 1024 * 1024); + let request = ResponsesApiRequest { + model: "gpt-test".into(), + input: vec![ResponseItem::Message { + id: None, + role: "user".into(), + content: vec![ContentItem::InputText { text: large_input }], + phase: None, + internal_chat_message_metadata_passthrough: None, + }], + tools: None, + tool_choice: "auto".into(), + parallel_tool_calls: false, + reasoning: None, + store: false, + stream: true, + stream_options: None, + include: Vec::new(), + service_tier: Some("priority".into()), + prompt_cache_key: None, + text: None, + client_metadata: None, + access_programs: None, + }; + let expected = serde_json::to_value(&request)?; + + let _stream = client + .stream_request(request, ResponsesOptions::default()) + .await?; + + let requests = state.take_stream_requests(); + assert_eq!(requests.len(), 1); + let body = std::str::from_utf8(request_body_bytes(&requests[0]))?; + let input_position = body.find(r#""input":"#).expect("input should be present"); + for routing_field in [r#""model":"#, r#""stream":"#, r#""service_tier":"#] { + assert!( + body.find(routing_field) + .is_some_and(|position| position < input_position), + "{routing_field} should precede input" + ); + } + assert!(body.starts_with( + r#"{"model":"gpt-test","stream":true,"service_tier":"priority","input":[{"type":"message""# + )); + assert!(body.len() > 2 * 1024 * 1024); + assert_eq!(serde_json::from_str::(body)?, expected); + Ok(()) +} + #[tokio::test] async fn streaming_client_adds_auth_headers() -> Result<()> { let state = RecordingState::default(); @@ -433,7 +489,6 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> { let request = ResponsesApiRequest { model: "gpt-test".into(), - instructions: "Say hi".into(), input: Vec::new(), tools: Some(empty_tools().into()), tool_choice: "auto".into(), @@ -548,7 +603,6 @@ async fn azure_store_sends_ids_and_headers() -> Result<()> { let request = ResponsesApiRequest { model: "gpt-test".into(), - instructions: "Say hi".into(), input: vec![ResponseItem::Message { id: Some(ResponseItemId::with_suffix("msg", "1")), role: "user".into(), diff --git a/vendor/codex-api/tests/realtime_websocket_e2e.rs b/vendor/codex-api/tests/realtime_websocket_e2e.rs index bd6f463..37a028e 100644 --- a/vendor/codex-api/tests/realtime_websocket_e2e.rs +++ b/vendor/codex-api/tests/realtime_websocket_e2e.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::future::Future; use std::time::Duration; +use codex_api::ApiError; use codex_api::Provider; use codex_api::RealtimeAudioFrame; use codex_api::RealtimeEvent; @@ -23,11 +24,15 @@ use codex_protocol::protocol::RealtimeVoice; use futures::SinkExt; use futures::StreamExt; use http::HeaderMap; +use http::StatusCode; use serde_json::Value; use serde_json::json; use tokio::net::TcpListener; use tokio_tungstenite::accept_async; +use tokio_tungstenite::accept_hdr_async; use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::tungstenite::handshake::server::Request; +use tokio_tungstenite::tungstenite::handshake::server::Response; type RealtimeWsStream = tokio_tungstenite::WebSocketStream; @@ -287,6 +292,86 @@ async fn realtime_ws_connect_webrtc_sideband_retries_join_until_server_is_availa server.await.expect("server task"); } +#[tokio::test] +async fn realtime_ws_existing_call_retries_activation_404_with_a_bounded_budget() { + for (status, failures, activates) in [ + (StatusCode::NOT_FOUND, 3, true), + (StatusCode::NOT_FOUND, 4, false), + (StatusCode::GONE, 1, false), + ] { + tokio::time::timeout(Duration::from_secs(5), async { + let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind"); + let addr = listener.local_addr().expect("address"); + let server = tokio::spawn(async move { + for _ in 0..failures { + let (stream, _) = listener.accept().await.expect("accept handshake"); + accept_hdr_async(stream, move |request: &Request, _: Response| { + assert!( + request + .uri() + .query() + .expect("query") + .contains("call_id=rtc_prepared") + ); + Err(http::Response::builder() + .status(status) + .body(None) + .expect("error response")) + }) + .await + .expect_err("call is not active"); + } + if activates { + let (stream, _) = listener.accept().await.expect("accept activated call"); + let mut ws = accept_async(stream).await.expect("successful handshake"); + // Existing-call attachment must not overwrite the prepared session. + assert!(matches!( + ws.next().await.expect("close").expect("frame"), + Message::Close(_) + )); + } + }); + let mut provider = test_provider(format!("http://{addr}")); + provider.retry.max_attempts = 0; + let client = + test_client(provider).with_webrtc_sideband_base_url(format!("http://{addr}")); + let result = client + .connect_existing_call_sideband( + RealtimeSessionConfig { + instructions: "unused existing-call prompt".to_string(), + initial_items: Vec::new(), + delegation_ack_filler: None, + model: None, + session_id: None, + event_parser: RealtimeEventParser::RealtimeV2, + session_mode: RealtimeSessionMode::Conversational, + output_modality: RealtimeOutputModality::Audio, + voice: RealtimeVoice::Marin, + }, + "rtc_prepared", + HeaderMap::new(), + HeaderMap::new(), + RealtimeTranscriptState::default(), + ) + .await; + if activates { + result + .expect("connect after activation") + .close() + .await + .expect("close"); + } else { + assert!( + matches!(result, Err(ApiError::Api { status: actual, .. }) if actual == status) + ); + } + server.await.expect("server task"); + }) + .await + .expect("bounded sideband handshake attempts"); + } +} + #[tokio::test] async fn realtime_ws_e2e_send_while_next_event_waits() { let (addr, server) = spawn_realtime_ws_server(|mut ws: RealtimeWsStream| async move { diff --git a/vendor/raw-transport.patch b/vendor/raw-transport.patch index 65a0d5b..d593457 100644 --- a/vendor/raw-transport.patch +++ b/vendor/raw-transport.patch @@ -75,7 +75,7 @@ +} --- a/src/endpoint/realtime_websocket/methods.rs +++ b/src/endpoint/realtime_websocket/methods.rs -@@ -62,6 +62,8 @@ +@@ -63,6 +63,8 @@ const OPENAI_REALTIME_API_BASE_URL: &str = "https://api.openai.com/v1"; const MAX_ACTIVE_TRANSCRIPT_BYTES: usize = 8 * 1024; const TRUNCATED_TRANSCRIPT_PREFIX: &str = "…"; @@ -84,7 +84,7 @@ struct WsStream { tx_command: mpsc::Sender, -@@ -856,6 +858,39 @@ +@@ -857,6 +859,39 @@ self } @@ -124,7 +124,7 @@ pub async fn connect( &self, config: RealtimeSessionConfig, -@@ -1014,17 +1049,83 @@ +@@ -1028,17 +1063,83 @@ session_initialization: RealtimeSessionInitialization, transcript_state: RealtimeTranscriptState, ) -> Result { @@ -213,7 +213,7 @@ request.headers_mut().extend(headers); info!("connecting realtime websocket: {ws_url}"); -@@ -1052,52 +1153,30 @@ +@@ -1066,52 +1167,30 @@ let (stream, response) = connector .connect(request, websocket_config()) .await @@ -319,8 +319,8 @@ body: EncodedJsonBody, --- a/src/endpoint/responses_websocket.rs +++ b/src/endpoint/responses_websocket.rs -@@ -418,6 +418,50 @@ - )) +@@ -407,6 +407,52 @@ + .await } + /// Gateway extension: expose the original handshake without the lossy event adapter. @@ -364,14 +364,16 @@ + let mut headers = + merge_request_headers(&self.provider.headers, extra_headers, default_headers); + self.auth.add_auth_headers(&mut headers); -+ connect_websocket_raw(url, headers, http_client_factory).await ++ let connector = WebSocketConnector::new(http_client_factory) ++ .map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?; ++ connect_websocket_raw(url, headers, &connector).await + } + /// Opens a WebSocket connection long enough to validate the upgrade response. /// /// The probe uses the same URL construction, headers, authentication, TLS, -@@ -501,28 +545,7 @@ - http_client_factory: &HttpClientFactory, +@@ -492,26 +538,7 @@ + connector: &WebSocketConnector, turn_state: Option>>, ) -> Result<(WsStream, StatusCode, bool, Option), ApiError> { - info!("connecting to websocket: {url}"); @@ -382,8 +384,6 @@ - .map_err(|err| ApiError::Stream(format!("failed to build websocket request: {err}")))?; - request.headers_mut().extend(headers); - -- let connector = WebSocketConnector::new(http_client_factory) -- .map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?; - let response = connector.connect(request, websocket_config()).await; - - let (stream, response) = match response { @@ -396,11 +396,11 @@ - return Err(map_ws_error(err, &url)); - } - }; -+ let (stream, response) = connect_websocket_raw(url, headers, http_client_factory).await?; ++ let (stream, response) = connect_websocket_raw(url, headers, connector).await?; let reasoning_included = response.headers().contains_key(X_REASONING_INCLUDED_HEADER); let server_model = response -@@ -544,6 +567,37 @@ +@@ -533,6 +560,35 @@ reasoning_included, server_model, )) @@ -409,7 +409,7 @@ +async fn connect_websocket_raw( + url: Url, + headers: HeaderMap, -+ http_client_factory: &HttpClientFactory, ++ connector: &WebSocketConnector, +) -> Result<(WebSocketConnection, http::Response>>), ApiError> { + info!("connecting to websocket: {url}"); + @@ -419,8 +419,6 @@ + .map_err(|err| ApiError::Stream(format!("failed to build websocket request: {err}")))?; + request.headers_mut().extend(headers); + -+ let connector = WebSocketConnector::new(http_client_factory) -+ .map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?; + let response = connector.connect(request, websocket_config()).await; + + let (stream, response) = match response {