diff --git a/Cargo.lock b/Cargo.lock index aa4989a..81a6144 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" dependencies = [ "anyhow", "base64 0.22.1", @@ -1481,7 +1483,7 @@ dependencies = [ [[package]] name = "codex-api" -version = "0.160.0" +version = "0.162.0" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" [[package]] name = "codex-config" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" dependencies = [ "tracing", ] [[package]] name = "codex-tools" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" [[package]] name = "codex-utils-pty" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" [[package]] name = "codex-utils-string" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" [[package]] name = "codex-websocket-auth" -version = "0.160.0" -source = "git+https://github.com/openai/codex.git?rev=a956835d020762cb2b570053af06f643a11c0ecc#a956835d020762cb2b570053af06f643a11c0ecc" +version = "0.162.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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.0" +source = "git+https://github.com/openai/codex.git?rev=c1382380de69521303b416720a52f42d51af6248#c1382380de69521303b416720a52f42d51af6248" 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..e77443b 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 = "c1382380de69521303b416720a52f42d51af6248" } +codex-websocket-client = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } 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 = "c1382380de69521303b416720a52f42d51af6248" } +codex-client = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-http-client = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-login = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-model-provider = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-model-provider-info = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-models-manager = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-protocol = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-config = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } -codex-features = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-features = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } -codex-tools = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-tools = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } -codex-exec-server = { git = "https://github.com/openai/codex.git", rev = "a956835d020762cb2b570053af06f643a11c0ecc" } +codex-exec-server = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } [dev-dependencies] reqwest = { version = "0.12", features = ["json", "stream", "multipart"] } diff --git a/README.md b/README.md index ae9da4f..224c7b4 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.0** 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..7695fa6 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.0", + "tag": "rust-v0.162.0", + "tag_object": "1f3f93473394b620b35580859b7e6864f7a9f948", + "commit": "c1382380de69521303b416720a52f42d51af6248", + "published_at": "2026-10-08T18:55:59Z", + "release_url": "https://github.com/openai/codex/releases/tag/rust-v0.162.0", "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.0/bwrap-x86_64-unknown-linux-musl.tar.gz", + "sha256": "b1df5504149571dea01ed01005a480d9aeb990ae53938dc1546663428b7b169f", "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.0/codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz", + "sha256": "dee8bf3df637c68e1495b928d643b76d4ad44f94e4f0ed79fe8482fa79e23d97", "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.0/codex-x86_64-unknown-linux-musl.tar.gz", + "sha256": "8daf67f6261161aa5939d8d42a516032d760406216779d7ce6040f242140ff73" } } diff --git a/docs/codex-models.md b/docs/codex-models.md index 20ea51b..87a14ed 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.0`. 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.0`. 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..c41638c 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.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. 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..19c91c0 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.0**, not every OpenAI service or future Codex release. diff --git a/docs/health.md b/docs/health.md index 37dc9ea..0e27c2e 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.0","codex_revision":"687a119f0fcaace47e1f1abcc77cec6c813fd6da"} ``` 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..ebe6840 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.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. ## Public adapters diff --git a/docs/models.md b/docs/models.md index b18eab9..6f88b9e 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.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. 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..e34bc7c 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.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. | Gateway prefix | Upstream prefix | | --- | --- | diff --git a/docs/realtime.md b/docs/realtime.md index a221fc1..cb5e877 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.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. [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..fd33f35 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.0 (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/src/lib.rs b/src/lib.rs index b7cd546..517b786 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.0"; +pub const CODEX_REV: &str = "c1382380de69521303b416720a52f42d51af6248"; diff --git a/src/lite_instructions.rs b/src/lite_instructions.rs index 8e8e547..3530c5e 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.0 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..fa8302e 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.0" ); 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..dba7bec 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.0; workspace dependencies expanded. [package] name = "codex-api" -version = "0.160.0" +version = "0.162.0" 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 = "c1382380de69521303b416720a52f42d51af6248" } +codex-http-client = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-protocol = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-utils-rustls-provider = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } +codex-websocket-client = { git = "https://github.com/openai/codex.git", rev = "c1382380de69521303b416720a52f42d51af6248" } 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 {