diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 8e84f041c3..7ce3ae47fa 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -684,6 +684,7 @@ "bytes_1.11.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"extra-platforms\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.60\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "bytestring_1.5.0": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{\"serde\":[\"dep:serde_core\"]}}", "bzip2-sys_0.1.13+1.0.8": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"}],\"features\":{\"__disabled\":[],\"static\":[]}}", + "bzip2_0.4.4": "{\"dependencies\":[{\"name\":\"bzip2-sys\",\"req\":\"^0.1.11\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"features\":[\"quickcheck\"],\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck6\",\"package\":\"quickcheck\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"tokio-core\",\"req\":\"^0.1\"},{\"name\":\"tokio-io\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{\"static\":[\"bzip2-sys/static\"],\"tokio\":[\"tokio-io\",\"futures\"]}}", "bzip2_0.5.2": "{\"dependencies\":[{\"name\":\"bzip2-sys\",\"optional\":true,\"req\":\"^0.1.13\"},{\"default_features\":false,\"features\":[\"rust-allocator\",\"semver-prefix\"],\"name\":\"libbz2-rs-sys\",\"optional\":true,\"req\":\"^0.1.3\"},{\"features\":[\"quickcheck1\"],\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5.4\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"default\":[\"dep:bzip2-sys\"],\"libbz2-rs-sys\":[\"dep:libbz2-rs-sys\",\"bzip2-sys?/__disabled\"],\"static\":[\"bzip2-sys?/static\"]}}", "cached_0.56.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.6\"},{\"name\":\"async-trait\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"cached_proc_macro\",\"optional\":true,\"req\":\"^0.25.0\"},{\"name\":\"cached_proc_macro_types\",\"optional\":true,\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"copy_dir\",\"req\":\"^0.1.3\"},{\"name\":\"directories\",\"optional\":true,\"req\":\"^6.0\"},{\"default_features\":false,\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"googletest\",\"req\":\"^0.11.0\"},{\"default_features\":false,\"features\":[\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15\"},{\"name\":\"once_cell\",\"req\":\"^1\"},{\"name\":\"r2d2\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"r2d2\"],\"name\":\"redis\",\"optional\":true,\"req\":\"^0.32\"},{\"name\":\"rmp-serde\",\"optional\":true,\"req\":\"^1.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"name\":\"sled\",\"optional\":true,\"req\":\"^0.34\"},{\"kind\":\"dev\",\"name\":\"smartstring\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"macros\",\"time\",\"sync\",\"parking_lot\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"web-time\",\"req\":\"^1.1.0\"}],\"features\":{\"ahash\":[\"dep:ahash\",\"hashbrown/default\"],\"async\":[\"futures\",\"tokio\",\"async-trait\"],\"async_tokio_rt_multi_thread\":[\"async\",\"tokio/rt-multi-thread\"],\"default\":[\"proc_macro\",\"ahash\"],\"disk_store\":[\"sled\",\"serde\",\"rmp-serde\",\"directories\"],\"proc_macro\":[\"cached_proc_macro\",\"cached_proc_macro_types\"],\"redis_ahash\":[\"redis_store\",\"redis/ahash\"],\"redis_async_std\":[\"redis_store\",\"async\",\"redis/aio\",\"redis/async-std-comp\",\"redis/tls\",\"redis/async-std-tls-comp\"],\"redis_connection_manager\":[\"redis_store\",\"redis/connection-manager\"],\"redis_store\":[\"redis\",\"r2d2\",\"serde\",\"serde_json\"],\"redis_tokio\":[\"redis_store\",\"async\",\"redis/aio\",\"redis/tokio-comp\",\"redis/tls\",\"redis/tokio-native-tls-comp\"],\"wasm\":[]}}", "cached_proc_macro_0.25.0": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.20.8\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.49\"},{\"name\":\"quote\",\"req\":\"^1.0.6\"},{\"name\":\"syn\",\"req\":\"^2.0.52\"}],\"features\":{}}", @@ -694,6 +695,7 @@ "cc_1.2.55": "{\"dependencies\":[{\"name\":\"find-msvc-tools\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"jobserver\",\"optional\":true,\"req\":\"^0.1.30\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(unix)\"},{\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"jobserver\":[],\"parallel\":[\"dep:libc\",\"dep:jobserver\"]}}", "cesu8_1.1.0": "{\"dependencies\":[],\"features\":{\"unstable\":[]}}", "cexpr_0.6.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"clang-sys\",\"req\":\">=0.13.0, <0.29.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7\"}],\"features\":{}}", + "cfg-expr_0.20.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.7\"},{\"name\":\"smallvec\",\"req\":\"^1.15\"},{\"name\":\"target-lexicon\",\"optional\":true,\"req\":\"=0.13.3\"}],\"features\":{\"default\":[],\"targets\":[\"target-lexicon\"]}}", "cfg-if_1.0.4": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"rustc-dep-of-std\":[\"core\"]}}", "cfg_aliases_0.1.1": "{\"dependencies\":[],\"features\":{}}", "cfg_aliases_0.2.1": "{\"dependencies\":[],\"features\":{}}", @@ -712,6 +714,7 @@ "clipboard-win_5.4.1": "{\"dependencies\":[{\"name\":\"error-code\",\"req\":\"^3\",\"target\":\"cfg(windows)\"},{\"name\":\"windows-win\",\"optional\":true,\"req\":\"^3\",\"target\":\"cfg(windows)\"}],\"features\":{\"monitor\":[\"windows-win\"],\"std\":[\"error-code/std\"]}}", "cmake_0.1.57": "{\"dependencies\":[{\"name\":\"cc\",\"req\":\"^1.2.46\"}],\"features\":{}}", "cmp_any_0.8.1": "{\"dependencies\":[],\"features\":{}}", + "codespan-reporting_0.13.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.6.3\"},{\"kind\":\"dev\",\"name\":\"peg\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"pico-args\",\"req\":\"^0.5.0\"},{\"kind\":\"dev\",\"name\":\"rustyline\",\"req\":\"^6\"},{\"default_features\":false,\"features\":[\"derive\",\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"termcolor\",\"optional\":true,\"req\":\"^1.0.4\"},{\"name\":\"unicode-width\",\"req\":\">=0.1, <0.3\"},{\"kind\":\"dev\",\"name\":\"unindent\",\"req\":\"^0.1\"}],\"features\":{\"ascii-only\":[],\"default\":[\"std\",\"termcolor\"],\"serialization\":[\"serde\"],\"std\":[\"serde?/std\"],\"termcolor\":[\"std\",\"dep:termcolor\"]}}", "color-eyre_0.6.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ansi-parser\",\"req\":\"^0.8.0\"},{\"name\":\"backtrace\",\"req\":\"^0.3.59\"},{\"name\":\"color-spantrace\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"eyre\",\"req\":\"^0.6\"},{\"name\":\"indenter\",\"req\":\"^0.3.0\"},{\"name\":\"once_cell\",\"req\":\"^1.18.0\"},{\"name\":\"owo-colors\",\"req\":\"^4.0\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^1.0.19\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.13\"},{\"name\":\"tracing-error\",\"optional\":true,\"req\":\"^0.2.0\"},{\"features\":[\"env-filter\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.1.1\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.15\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"}],\"features\":{\"capture-spantrace\":[\"tracing-error\",\"color-spantrace\"],\"default\":[\"track-caller\",\"capture-spantrace\"],\"issue-url\":[\"url\"],\"track-caller\":[]}}", "color-spantrace_0.3.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ansi-parser\",\"req\":\"^0.8\"},{\"name\":\"once_cell\",\"req\":\"^1.18.0\"},{\"name\":\"owo-colors\",\"req\":\"^4.0\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.29\"},{\"name\":\"tracing-core\",\"req\":\"^0.1.21\"},{\"name\":\"tracing-error\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.4\"}],\"features\":{}}", "color_quant_1.1.0": "{\"dependencies\":[],\"features\":{}}", @@ -724,6 +727,7 @@ "const-oid_0.9.6": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"}],\"features\":{\"db\":[],\"std\":[]}}", "const_format_0.2.35": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"arrayvec\",\"req\":\"^0.7.0\"},{\"name\":\"const_format_proc_macros\",\"req\":\"=0.2.34\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^1.3.5\"},{\"default_features\":false,\"name\":\"konst\",\"optional\":true,\"req\":\"^0.2.13\"}],\"features\":{\"__debug\":[\"const_format_proc_macros/debug\"],\"__docsrs\":[],\"__inline_const_pat_tests\":[\"__test\",\"fmt\"],\"__only_new_tests\":[\"__test\"],\"__test\":[],\"all\":[\"fmt\",\"derive\",\"rust_1_64\",\"assert\"],\"assert\":[\"assertc\"],\"assertc\":[\"fmt\",\"assertcp\"],\"assertcp\":[\"rust_1_51\"],\"const_generics\":[\"rust_1_51\"],\"constant_time_as_str\":[\"fmt\"],\"default\":[],\"derive\":[\"fmt\",\"const_format_proc_macros/derive\"],\"fmt\":[\"rust_1_83\"],\"more_str_macros\":[\"rust_1_64\"],\"nightly_const_generics\":[\"const_generics\"],\"rust_1_51\":[],\"rust_1_64\":[\"rust_1_51\",\"konst\",\"konst/rust_1_64\"],\"rust_1_83\":[\"rust_1_64\"]}}", "const_format_proc_macros_0.2.34": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^1.3.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.19\"},{\"name\":\"quote\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\"],\"name\":\"syn\",\"optional\":true,\"req\":\"^1.0.38\"},{\"name\":\"unicode-xid\",\"req\":\"^0.2\"}],\"features\":{\"all\":[\"derive\"],\"debug\":[\"syn/extra-traits\"],\"default\":[],\"derive\":[\"syn\",\"syn/derive\",\"syn/printing\"]}}", + "constant_time_eq_0.1.5": "{\"dependencies\":[],\"features\":{}}", "constant_time_eq_0.3.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"count_instructions\",\"req\":\"^0.1.3\"},{\"features\":[\"cargo_bench_support\",\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"}],\"features\":{\"count_instructions_test\":[]}}", "convert_case_0.10.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"unicode-segmentation\",\"req\":\"^1.9.0\"}],\"features\":{}}", "convert_case_0.6.0": "{\"dependencies\":[{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.18.0\"},{\"kind\":\"dev\",\"name\":\"strum_macros\",\"req\":\"^0.18.0\"},{\"name\":\"unicode-segmentation\",\"req\":\"^1.9.0\"}],\"features\":{\"random\":[\"rand\"]}}", @@ -754,6 +758,11 @@ "ctor_0.6.3": "{\"dependencies\":[{\"name\":\"ctor-proc-macro\",\"optional\":true,\"req\":\"=0.0.7\"},{\"default_features\":false,\"name\":\"dtor\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"libc-print\",\"req\":\"^0.1.20\"}],\"features\":{\"__no_warn_on_missing_unsafe\":[\"dtor?/__no_warn_on_missing_unsafe\"],\"default\":[\"dtor\",\"proc_macro\",\"__no_warn_on_missing_unsafe\"],\"dtor\":[\"dep:dtor\"],\"proc_macro\":[\"dep:ctor-proc-macro\",\"dtor?/proc_macro\"],\"used_linker\":[\"dtor?/used_linker\"]}}", "curve25519-dalek-derive_0.1.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.66\"},{\"name\":\"quote\",\"req\":\"^1.0.31\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.27\"}],\"features\":{}}", "curve25519-dalek_4.1.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1\"},{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2.6\",\"target\":\"cfg(target_arch = \\\"x86_64\\\")\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"curve25519-dalek-derive\",\"req\":\"^0.1\",\"target\":\"cfg(all(not(curve25519_dalek_backend = \\\"fiat\\\"), not(curve25519_dalek_backend = \\\"serial\\\"), target_arch = \\\"x86_64\\\"))\"},{\"default_features\":false,\"name\":\"digest\",\"optional\":true,\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"ff\",\"optional\":true,\"req\":\"^0.13\"},{\"default_features\":false,\"name\":\"fiat-crypto\",\"req\":\"^0.2.1\",\"target\":\"cfg(curve25519_dalek_backend = \\\"fiat\\\")\"},{\"default_features\":false,\"name\":\"group\",\"optional\":true,\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6.4\"},{\"default_features\":false,\"features\":[\"getrandom\"],\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.3.0\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"zeroize?/alloc\"],\"default\":[\"alloc\",\"precomputed-tables\",\"zeroize\"],\"group\":[\"dep:group\",\"rand_core\"],\"group-bits\":[\"group\",\"ff/bits\"],\"legacy_compatibility\":[],\"precomputed-tables\":[]}}", + "cxx-build_1.0.194": "{\"dependencies\":[{\"name\":\"cc\",\"req\":\"^1.0.101\"},{\"name\":\"codespan-reporting\",\"req\":\"^0.13.1\"},{\"kind\":\"dev\",\"name\":\"cxx\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"cxx-gen\",\"req\":\"^0.7\"},{\"name\":\"indexmap\",\"req\":\"^2.9.0\"},{\"kind\":\"dev\",\"name\":\"pkg-config\",\"req\":\"^0.3.27\"},{\"default_features\":false,\"features\":[\"span-locations\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"scratch\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"full\",\"parsing\",\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{\"parallel\":[\"cc/parallel\"]}}", + "cxx_1.0.194": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.101\"},{\"kind\":\"dev\",\"name\":\"cc\",\"req\":\"^1.0.101\"},{\"kind\":\"build\",\"name\":\"cxx-build\",\"req\":\"=1.0.194\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"cxx-build\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"cxx-gen\",\"req\":\"=0.7.194\"},{\"kind\":\"dev\",\"name\":\"cxx-test-suite\",\"req\":\"^0\"},{\"kind\":\"build\",\"name\":\"cxxbridge-cmd\",\"req\":\"=1.0.194\",\"target\":\"cfg(any())\"},{\"default_features\":false,\"kind\":\"build\",\"name\":\"cxxbridge-flags\",\"req\":\"=1.0.194\"},{\"name\":\"cxxbridge-macro\",\"req\":\"=1.0.194\"},{\"default_features\":false,\"name\":\"foldhash\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"indoc\",\"req\":\"^2\"},{\"name\":\"link-cplusplus\",\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.95\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.40\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"kind\":\"dev\",\"name\":\"scratch\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"target-triple\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.8\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"alloc\":[],\"c++14\":[\"cxxbridge-flags/c++14\"],\"c++17\":[\"cxxbridge-flags/c++17\"],\"c++20\":[\"cxxbridge-flags/c++20\"],\"default\":[\"std\",\"cxxbridge-flags/default\"],\"std\":[\"alloc\",\"foldhash/std\"]}}", + "cxxbridge-cmd_1.0.194": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"error-context\",\"help\",\"std\",\"suggestions\",\"usage\"],\"name\":\"clap\",\"req\":\"^4.3.11\"},{\"name\":\"codespan-reporting\",\"req\":\"^0.13.1\"},{\"name\":\"indexmap\",\"req\":\"^2.9.0\"},{\"default_features\":false,\"features\":[\"span-locations\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"full\",\"parsing\",\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}", + "cxxbridge-flags_1.0.194": "{\"dependencies\":[],\"features\":{\"c++14\":[],\"c++17\":[],\"c++20\":[],\"default\":[]}}", + "cxxbridge-macro_1.0.194": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cxx\",\"req\":\"^1.0\"},{\"name\":\"indexmap\",\"req\":\"^2.9.0\"},{\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"^0.2.35\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}", "darling_0.20.11": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.20.11\"},{\"name\":\"darling_macro\",\"req\":\"=0.20.11\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.9\",\"target\":\"cfg(compiletests)\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.15\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\",\"target\":\"cfg(compiletests)\"}],\"features\":{\"default\":[\"suggestions\"],\"diagnostics\":[\"darling_core/diagnostics\"],\"suggestions\":[\"darling_core/suggestions\"]}}", "darling_0.21.3": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.21.3\"},{\"name\":\"darling_macro\",\"req\":\"=0.21.3\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.9\",\"target\":\"cfg(compiletests)\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.15\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\",\"target\":\"cfg(compiletests)\"}],\"features\":{\"default\":[\"suggestions\"],\"diagnostics\":[\"darling_core/diagnostics\"],\"serde\":[\"darling_core/serde\"],\"suggestions\":[\"darling_core/suggestions\"]}}", "darling_0.23.0": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.23.0\"},{\"name\":\"darling_macro\",\"req\":\"=0.23.0\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.9\",\"target\":\"cfg(compiletests)\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.15\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\",\"target\":\"cfg(compiletests)\"}],\"features\":{\"default\":[\"suggestions\"],\"diagnostics\":[\"darling_core/diagnostics\"],\"serde\":[\"darling_core/serde\"],\"suggestions\":[\"darling_core/suggestions\"]}}", @@ -852,6 +861,7 @@ "foreign-types-shared_0.1.1": "{\"dependencies\":[],\"features\":{}}", "foreign-types_0.3.2": "{\"dependencies\":[{\"name\":\"foreign-types-shared\",\"req\":\"^0.1\"}],\"features\":{}}", "form_urlencoded_1.2.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"percent-encoding\",\"req\":\"^2.3.0\"}],\"features\":{\"alloc\":[\"percent-encoding/alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\",\"percent-encoding/std\"]}}", + "fs2_0.4.3": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.30\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3\"},{\"features\":[\"handleapi\",\"processthreadsapi\",\"winerror\",\"fileapi\",\"winbase\",\"std\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "fs_extra_1.3.0": "{\"dependencies\":[],\"features\":{}}", "fsevent-sys_4.1.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.68\"}],\"features\":{}}", "futures-channel_0.3.31": "{\"dependencies\":[{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"futures-sink\",\"optional\":true,\"req\":\"^0.3.31\"}],\"features\":{\"alloc\":[\"futures-core/alloc\"],\"cfg-target-has-atomic\":[],\"default\":[\"std\"],\"sink\":[\"futures-sink\"],\"std\":[\"alloc\",\"futures-core/std\"],\"unstable\":[]}}", @@ -874,15 +884,25 @@ "getrandom_0.3.4": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.77\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"netbsd\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"solaris\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"vxworks\\\")\"},{\"default_features\":false,\"name\":\"r-efi\",\"req\":\"^5.1\",\"target\":\"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\"},{\"default_features\":false,\"name\":\"wasip2\",\"req\":\"^1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.98\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"}],\"features\":{\"std\":[],\"wasm_js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"]}}", "gif_0.14.1": "{\"dependencies\":[{\"name\":\"color_quant\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"png\",\"req\":\"^0.18.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.10.0\"},{\"name\":\"weezl\",\"req\":\"^0.1.10\"}],\"features\":{\"color_quant\":[\"dep:color_quant\"],\"default\":[\"raii_no_panic\",\"std\",\"color_quant\"],\"raii_no_panic\":[],\"std\":[]}}", "gimli_0.32.3": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"test-assembler\",\"req\":\"^0.1.3\"}],\"features\":{\"default\":[\"read-all\",\"write\"],\"endian-reader\":[\"read\",\"dep:stable_deref_trait\"],\"fallible-iterator\":[\"dep:fallible-iterator\"],\"read\":[\"read-core\"],\"read-all\":[\"read\",\"std\",\"fallible-iterator\",\"endian-reader\"],\"read-core\":[],\"rustc-dep-of-std\":[\"dep:core\",\"dep:alloc\"],\"std\":[\"fallible-iterator?/std\",\"stable_deref_trait?/std\"],\"write\":[\"dep:indexmap\"]}}", + "gio-sys_0.21.5": "{\"dependencies\":[{\"name\":\"glib-sys\",\"req\":\"^0.21\"},{\"name\":\"gobject-sys\",\"req\":\"^0.21\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"v2_58\":[],\"v2_60\":[\"v2_58\"],\"v2_62\":[\"v2_60\"],\"v2_64\":[\"v2_62\"],\"v2_66\":[\"v2_64\"],\"v2_68\":[\"v2_66\"],\"v2_70\":[\"v2_68\"],\"v2_72\":[\"v2_70\"],\"v2_74\":[\"v2_72\"],\"v2_76\":[\"v2_74\"],\"v2_78\":[\"v2_76\"],\"v2_80\":[\"v2_78\"],\"v2_82\":[\"v2_80\"],\"v2_84\":[\"v2_82\"],\"v2_86\":[\"v2_84\"]}}", "git+https://github.com/dzbarsky/rules_rust?rev=b56cbaa8465e74127f1ea216f813cd377295ad81#b56cbaa8465e74127f1ea216f813cd377295ad81_runfiles": "{\"dependencies\":[],\"features\":{},\"strip_prefix\":\"\"}", "git+https://github.com/helix-editor/nucleo.git?rev=4253de9faabb4e5c6d81d946a5e35a90f87347ee#4253de9faabb4e5c6d81d946a5e35a90f87347ee_nucleo": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"nucleo-matcher\",\"optional\":false},{\"default_features\":true,\"features\":[\"send_guard\",\"arc_lock\"],\"name\":\"parking_lot\",\"optional\":false},{\"name\":\"rayon\"}],\"features\":{},\"strip_prefix\":\"\"}", "git+https://github.com/helix-editor/nucleo.git?rev=4253de9faabb4e5c6d81d946a5e35a90f87347ee#4253de9faabb4e5c6d81d946a5e35a90f87347ee_nucleo-matcher": "{\"dependencies\":[{\"name\":\"memchr\"},{\"default_features\":true,\"features\":[],\"name\":\"unicode-segmentation\",\"optional\":true}],\"features\":{\"default\":[\"unicode-normalization\",\"unicode-casefold\",\"unicode-segmentation\"],\"unicode-casefold\":[],\"unicode-normalization\":[],\"unicode-segmentation\":[\"dep:unicode-segmentation\"]},\"strip_prefix\":\"matcher\"}", + "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0_libwebrtc": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"livekit-protocol\",\"optional\":false},{\"name\":\"log\"},{\"features\":[\"derive\"],\"name\":\"serde\"},{\"name\":\"serde_json\"},{\"name\":\"thiserror\"},{\"default_features\":true,\"features\":[],\"name\":\"glib\",\"optional\":true,\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"freebsd\\\"))\"},{\"name\":\"cxx\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"lazy_static\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":true,\"features\":[],\"name\":\"livekit-runtime\",\"optional\":false,\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"parking_lot\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rtrb\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"macros\",\"sync\"],\"name\":\"tokio\",\"optional\":false,\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":true,\"features\":[],\"name\":\"webrtc-sys\",\"optional\":false,\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"js-sys\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen-futures\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":true,\"features\":[\"MessageEvent\",\"RtcPeerConnection\",\"RtcSignalingState\",\"RtcSdpType\",\"RtcSessionDescriptionInit\",\"RtcPeerConnectionIceEvent\",\"RtcIceCandidate\",\"RtcDataChannel\",\"RtcDataChannelEvent\",\"RtcDataChannelState\",\"EventTarget\",\"WebGlRenderingContext\",\"WebGlTexture\"],\"name\":\"web-sys\",\"optional\":false,\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"jni\",\"target\":\"cfg(target_os = \\\"android\\\")\"}],\"features\":{\"default\":[\"glib-main-loop\"],\"glib-main-loop\":[\"dep:glib\"]},\"strip_prefix\":\"libwebrtc\"}", + "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0_livekit-protocol": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\"],\"name\":\"futures-util\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"livekit-runtime\",\"optional\":false},{\"name\":\"parking_lot\"},{\"name\":\"pbjson\"},{\"name\":\"pbjson-types\"},{\"name\":\"prost\"},{\"name\":\"serde\"},{\"name\":\"thiserror\"},{\"default_features\":false,\"features\":[\"macros\",\"rt\",\"sync\"],\"name\":\"tokio\",\"optional\":false}],\"features\":{},\"strip_prefix\":\"livekit-protocol\"}", + "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0_livekit-runtime": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"async-io\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"async-std\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"async-task\",\"optional\":true},{\"name\":\"futures\",\"optional\":true},{\"default_features\":false,\"features\":[\"net\",\"rt\",\"rt-multi-thread\",\"time\"],\"name\":\"tokio\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"tokio-stream\",\"optional\":true}],\"features\":{\"async\":[\"dep:async-std\",\"dep:futures\",\"dep:async-io\"],\"default\":[\"tokio\"],\"dispatcher\":[\"dep:futures\",\"dep:async-io\",\"dep:async-std\",\"dep:async-task\"],\"tokio\":[\"dep:tokio\",\"dep:tokio-stream\"]},\"strip_prefix\":\"livekit-runtime\"}", + "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0_webrtc-sys": "{\"dependencies\":[{\"name\":\"cxx\"},{\"name\":\"log\"},{\"kind\":\"build\",\"name\":\"cc\"},{\"kind\":\"build\",\"name\":\"cxx-build\"},{\"kind\":\"build\",\"name\":\"glob\"},{\"kind\":\"build\",\"name\":\"pkg-config\"},{\"default_features\":true,\"features\":[],\"kind\":\"build\",\"name\":\"webrtc-sys-build\",\"optional\":false}],\"features\":{\"default\":[]},\"strip_prefix\":\"webrtc-sys\"}", + "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0_webrtc-sys-build": "{\"dependencies\":[{\"name\":\"anyhow\"},{\"name\":\"fs2\"},{\"name\":\"regex\"},{\"default_features\":false,\"features\":[\"rustls-tls-native-roots\",\"blocking\"],\"name\":\"reqwest\",\"optional\":false},{\"name\":\"scratch\"},{\"name\":\"semver\"},{\"name\":\"zip\"}],\"features\":{},\"strip_prefix\":\"webrtc-sys/build\"}", "git+https://github.com/nornagon/crossterm?branch=nornagon%2Fcolor-query#87db8bfa6dc99427fd3b071681b07fc31c6ce995_crossterm": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"bitflags\",\"optional\":false},{\"default_features\":false,\"features\":[],\"name\":\"futures-core\",\"optional\":true},{\"name\":\"parking_lot\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"filedescriptor\",\"optional\":true,\"target\":\"cfg(unix)\"},{\"default_features\":false,\"features\":[],\"name\":\"libc\",\"optional\":true,\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[\"os-poll\"],\"name\":\"mio\",\"optional\":true,\"target\":\"cfg(unix)\"},{\"default_features\":false,\"features\":[\"std\",\"stdio\",\"termios\"],\"name\":\"rustix\",\"optional\":false,\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[],\"name\":\"signal-hook\",\"optional\":true,\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[\"support-v1_0\"],\"name\":\"signal-hook-mio\",\"optional\":true,\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[],\"name\":\"crossterm_winapi\",\"optional\":true,\"target\":\"cfg(windows)\"},{\"default_features\":true,\"features\":[\"winuser\",\"winerror\"],\"name\":\"winapi\",\"optional\":true,\"target\":\"cfg(windows)\"}],\"features\":{\"bracketed-paste\":[],\"default\":[\"bracketed-paste\",\"windows\",\"events\"],\"event-stream\":[\"dep:futures-core\",\"events\"],\"events\":[\"dep:mio\",\"dep:signal-hook\",\"dep:signal-hook-mio\"],\"serde\":[\"dep:serde\",\"bitflags/serde\"],\"use-dev-tty\":[\"filedescriptor\",\"rustix/process\"],\"windows\":[\"dep:winapi\",\"dep:crossterm_winapi\"]},\"strip_prefix\":\"\"}", "git+https://github.com/nornagon/ratatui?branch=nornagon-v0.29.0-patch#9b2ad1298408c45918ee9f8241a6f95498cdbed2_ratatui": "{\"dependencies\":[{\"name\":\"bitflags\"},{\"name\":\"cassowary\"},{\"name\":\"compact_str\"},{\"default_features\":true,\"features\":[],\"name\":\"crossterm\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"document-features\",\"optional\":true},{\"name\":\"indoc\"},{\"name\":\"instability\"},{\"name\":\"itertools\"},{\"name\":\"lru\"},{\"default_features\":true,\"features\":[],\"name\":\"palette\",\"optional\":true},{\"name\":\"paste\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"strum\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"termwiz\",\"optional\":true},{\"default_features\":true,\"features\":[\"local-offset\"],\"name\":\"time\",\"optional\":true},{\"name\":\"unicode-segmentation\"},{\"name\":\"unicode-truncate\"},{\"name\":\"unicode-width\"},{\"default_features\":true,\"features\":[],\"name\":\"termion\",\"optional\":true,\"target\":\"cfg(not(windows))\"}],\"features\":{\"all-widgets\":[\"widget-calendar\"],\"crossterm\":[\"dep:crossterm\"],\"default\":[\"crossterm\",\"underline-color\"],\"macros\":[],\"palette\":[\"dep:palette\"],\"scrolling-regions\":[],\"serde\":[\"dep:serde\",\"bitflags/serde\",\"compact_str/serde\"],\"termion\":[\"dep:termion\"],\"termwiz\":[\"dep:termwiz\"],\"underline-color\":[\"dep:crossterm\"],\"unstable\":[\"unstable-rendered-line-info\",\"unstable-widget-ref\",\"unstable-backend-writer\"],\"unstable-backend-writer\":[],\"unstable-rendered-line-info\":[],\"unstable-widget-ref\":[],\"widget-calendar\":[\"dep:time\"]},\"strip_prefix\":\"\"}", "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=132f5b39c862e3a970f731d709608b3e6276d5f6#132f5b39c862e3a970f731d709608b3e6276d5f6_tokio-tungstenite": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"optional\":false},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"default_features\":false,\"features\":[],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"tokio-native-tls\",\"optional\":true},{\"default_features\":false,\"features\":[],\"name\":\"tokio-rustls\",\"optional\":true},{\"default_features\":false,\"features\":[],\"name\":\"tungstenite\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\",\"tokio-rustls\",\"stream\",\"tungstenite/__rustls-tls\",\"handshake\"],\"connect\":[\"stream\",\"tokio/net\",\"handshake\"],\"default\":[\"connect\",\"handshake\"],\"handshake\":[\"tungstenite/handshake\"],\"native-tls\":[\"native-tls-crate\",\"tokio-native-tls\",\"stream\",\"tungstenite/native-tls\",\"handshake\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\",\"tungstenite/native-tls-vendored\"],\"proxy\":[\"tungstenite/proxy\",\"tokio/net\",\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"stream\":[],\"url\":[\"tungstenite/url\"]},\"strip_prefix\":\"\"}", "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=9200079d3b54a1ff51072e24d81fd354f085156f#9200079d3b54a1ff51072e24d81fd354f085156f_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true},{\"default_features\":false,\"features\":[\"zlib\"],\"name\":\"flate2\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"name\":\"rand\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true},{\"name\":\"thiserror\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true},{\"name\":\"utf-8\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"deflate\":[\"headers\",\"flate2\"],\"handshake\":[\"data-encoding\",\"headers\",\"httparse\",\"sha1\"],\"headers\":[\"http\",\"dep:headers\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}", + "glib-macros_0.21.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"glib\",\"req\":\"^0.21\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"name\":\"proc-macro-crate\",\"req\":\"^3.3\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.104\"},{\"kind\":\"dev\",\"name\":\"trybuild2\",\"req\":\"^1.2\"}],\"features\":{}}", + "glib-sys_0.21.5": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v2_58\":[],\"v2_60\":[\"v2_58\"],\"v2_62\":[\"v2_60\"],\"v2_64\":[\"v2_62\"],\"v2_66\":[\"v2_64\"],\"v2_68\":[\"v2_66\"],\"v2_70\":[\"v2_68\"],\"v2_72\":[\"v2_70\"],\"v2_74\":[\"v2_72\"],\"v2_76\":[\"v2_74\"],\"v2_78\":[\"v2_76\"],\"v2_80\":[\"v2_78\"],\"v2_82\":[\"v2_80\"],\"v2_84\":[\"v2_82\"],\"v2_86\":[\"v2_84\"]}}", + "glib_0.21.5": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.9\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"name\":\"futures-executor\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-task\",\"req\":\"^0.3\"},{\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"gio-sys\",\"optional\":true,\"req\":\"^0.21\"},{\"kind\":\"dev\",\"name\":\"gir-format-check\",\"req\":\"^0.1\"},{\"name\":\"glib-macros\",\"req\":\"^0.21\"},{\"name\":\"glib-sys\",\"req\":\"^0.21\"},{\"name\":\"gobject-sys\",\"req\":\"^0.21\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"memchr\",\"req\":\"^2.7.5\"},{\"name\":\"rs-log\",\"optional\":true,\"package\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"union\",\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"req\":\"^1.15\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"trybuild2\",\"req\":\"^1\"}],\"features\":{\"compiletests\":[],\"default\":[\"gio\"],\"gio\":[\"gio-sys\"],\"log\":[\"rs-log\"],\"log_macros\":[\"log\"],\"v2_58\":[\"glib-sys/v2_58\",\"gobject-sys/v2_58\"],\"v2_60\":[\"v2_58\",\"glib-sys/v2_60\"],\"v2_62\":[\"v2_60\",\"glib-sys/v2_62\",\"gobject-sys/v2_62\"],\"v2_64\":[\"v2_62\",\"glib-sys/v2_64\"],\"v2_66\":[\"v2_64\",\"glib-sys/v2_66\",\"gobject-sys/v2_66\"],\"v2_68\":[\"v2_66\",\"glib-sys/v2_68\",\"gobject-sys/v2_68\"],\"v2_70\":[\"v2_68\",\"glib-sys/v2_70\",\"gobject-sys/v2_70\"],\"v2_72\":[\"v2_70\",\"glib-sys/v2_72\",\"gobject-sys/v2_72\"],\"v2_74\":[\"v2_72\",\"glib-sys/v2_74\",\"gobject-sys/v2_74\"],\"v2_76\":[\"v2_74\",\"glib-sys/v2_76\",\"gobject-sys/v2_76\"],\"v2_78\":[\"v2_76\",\"glib-sys/v2_78\",\"gobject-sys/v2_78\"],\"v2_80\":[\"v2_78\",\"glib-sys/v2_80\",\"gobject-sys/v2_80\"],\"v2_82\":[\"v2_80\",\"glib-sys/v2_82\",\"gobject-sys/v2_82\"],\"v2_84\":[\"v2_82\",\"glib-sys/v2_84\",\"gobject-sys/v2_84\"],\"v2_86\":[\"v2_84\",\"glib-sys/v2_86\",\"gobject-sys/v2_86\"]}}", "glob_0.3.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3\"}],\"features\":{}}", "globset_0.4.18": "{\"dependencies\":[{\"name\":\"aho-corasick\",\"req\":\"^1.1.1\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex-syntax\",\"req\":\"^0.8.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.188\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.107\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"log\"],\"serde1\":[\"serde\"],\"simd-accel\":[]}}", + "gobject-sys_0.21.5": "{\"dependencies\":[{\"name\":\"glib-sys\",\"req\":\"^0.21\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v2_58\":[],\"v2_62\":[\"v2_58\"],\"v2_66\":[\"v2_62\"],\"v2_68\":[\"v2_66\"],\"v2_70\":[\"v2_68\"],\"v2_72\":[\"v2_70\"],\"v2_74\":[\"v2_72\"],\"v2_76\":[\"v2_74\"],\"v2_78\":[\"v2_74\"],\"v2_80\":[\"v2_78\"],\"v2_82\":[\"v2_80\"],\"v2_84\":[\"v2_82\"],\"v2_86\":[\"v2_84\"]}}", "h2_0.4.13": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", "half_2.7.1": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.4.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"crunchy\",\"req\":\"^0.2.2\",\"target\":\"cfg(target_arch = \\\"spirv\\\")\"},{\"kind\":\"dev\",\"name\":\"crunchy\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.16\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"thread_rng\"],\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"rand_distr\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"derive\",\"simd\"],\"name\":\"zerocopy\",\"req\":\"^0.8.26\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"nightly\":[],\"rand_distr\":[\"dep:rand\",\"dep:rand_distr\"],\"std\":[\"alloc\"],\"use-intrinsics\":[],\"zerocopy\":[]}}", "hashbrown_0.12.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"bumpalo\",\"optional\":true,\"req\":\"^3.5.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"ahash-compile-time-rng\":[\"ahash/compile-time-rng\"],\"default\":[\"ahash\",\"inline-more\"],\"inline-more\":[],\"nightly\":[],\"raw\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"compiler_builtins\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", @@ -892,6 +912,7 @@ "hashlink_0.10.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"serde_impl\":[\"serde\"]}}", "headers-core_0.3.0": "{\"dependencies\":[{\"name\":\"http\",\"req\":\"^1.0.0\"}],\"features\":{}}", "headers_0.4.1": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"headers-core\",\"req\":\"^0.3\"},{\"name\":\"http\",\"req\":\"^1.0.0\"},{\"name\":\"httpdate\",\"req\":\"^1\"},{\"name\":\"mime\",\"req\":\"^0.3.14\"},{\"name\":\"sha1\",\"req\":\"^0.10\"}],\"features\":{\"nightly\":[]}}", + "heck_0.4.1": "{\"dependencies\":[{\"name\":\"unicode-segmentation\",\"optional\":true,\"req\":\"^1.2.0\"}],\"features\":{\"default\":[],\"unicode\":[\"unicode-segmentation\"]}}", "heck_0.5.0": "{\"dependencies\":[],\"features\":{}}", "hermit-abi_0.5.2": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[],\"rustc-dep-of-std\":[\"core\",\"alloc\"]}}", "hex_0.4.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"faster-hex\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rustc-hex\",\"req\":\"^2.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", @@ -960,6 +981,7 @@ "is_ci_1.2.0": "{\"dependencies\":[],\"features\":{}}", "is_terminal_polyfill_1.70.2": "{\"dependencies\":[],\"features\":{\"default\":[]}}", "itertools_0.10.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"= 0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", + "itertools_0.11.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.13.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.14.0": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itoa_1.0.17": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", @@ -984,6 +1006,7 @@ "libredox_0.1.12": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"ioslice\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"redox_syscall\",\"optional\":true,\"req\":\"^0.7\"}],\"features\":{\"call\":[],\"default\":[\"call\",\"std\",\"redox_syscall\"],\"mkns\":[\"ioslice\"],\"std\":[]}}", "libsqlite3-sys_0.30.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.69\"},{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1.1.6\"},{\"name\":\"openssl-sys\",\"optional\":true,\"req\":\"^0.9.103\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"optional\":true,\"req\":\"^0.3.19\"},{\"kind\":\"build\",\"name\":\"prettyplease\",\"optional\":true,\"req\":\"^0.2.20\"},{\"default_features\":false,\"kind\":\"build\",\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.36\"},{\"features\":[\"full\",\"extra-traits\",\"visit-mut\"],\"kind\":\"build\",\"name\":\"syn\",\"optional\":true,\"req\":\"^2.0.72\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"optional\":true,\"req\":\"^0.2.15\"}],\"features\":{\"buildtime_bindgen\":[\"bindgen\",\"pkg-config\",\"vcpkg\"],\"bundled\":[\"cc\",\"bundled_bindings\"],\"bundled-sqlcipher\":[\"bundled\"],\"bundled-sqlcipher-vendored-openssl\":[\"bundled-sqlcipher\",\"openssl-sys/vendored\"],\"bundled-windows\":[\"cc\",\"bundled_bindings\"],\"bundled_bindings\":[],\"default\":[\"min_sqlite_version_3_14_0\"],\"in_gecko\":[],\"loadable_extension\":[\"prettyplease\",\"quote\",\"syn\"],\"min_sqlite_version_3_14_0\":[\"pkg-config\",\"vcpkg\"],\"preupdate_hook\":[\"buildtime_bindgen\"],\"session\":[\"preupdate_hook\",\"buildtime_bindgen\"],\"sqlcipher\":[],\"unlock_notify\":[],\"wasm32-wasi-vfs\":[],\"with-asan\":[]}}", "libz-sys_1.1.23": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.98\"},{\"kind\":\"build\",\"name\":\"cmake\",\"optional\":true,\"req\":\"^0.1.50\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.43\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2.11\"}],\"features\":{\"asm\":[],\"default\":[\"libc\",\"stock-zlib\"],\"static\":[],\"stock-zlib\":[],\"zlib-ng\":[\"libc\",\"cmake\"],\"zlib-ng-no-cmake-experimental-community-maintained\":[\"libc\"]}}", + "link-cplusplus_1.0.12": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1\"}],\"features\":{\"default\":[],\"libc++\":[],\"libcxx\":[\"libc++\"],\"libstdc++\":[],\"libstdcxx\":[\"libstdc++\"],\"nothing\":[]}}", "linked-hash-map_0.5.6": "{\"dependencies\":[{\"name\":\"heapsize\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"heapsize_impl\":[\"heapsize\"],\"nightly\":[],\"serde_impl\":[\"serde\"]}}", "linux-keyutils_0.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.4.11\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.132\"},{\"kind\":\"dev\",\"name\":\"zeroize\",\"req\":\"^1.5.7\"}],\"features\":{\"default\":[],\"std\":[\"bitflags/std\"]}}", "linux-raw-sys_0.11.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"auxvec\":[],\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"image\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", @@ -1094,11 +1117,16 @@ "parking_2.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{}}", "parking_lot_0.12.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"name\":\"lock_api\",\"req\":\"^0.4.14\"},{\"name\":\"parking_lot_core\",\"req\":\"^0.9.12\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"}],\"features\":{\"arc_lock\":[\"lock_api/arc_lock\"],\"deadlock_detection\":[\"parking_lot_core/deadlock_detection\"],\"default\":[],\"hardware-lock-elision\":[],\"nightly\":[\"parking_lot_core/nightly\",\"lock_api/nightly\"],\"owning_ref\":[\"lock_api/owning_ref\"],\"send_guard\":[],\"serde\":[\"lock_api/serde\"]}}", "parking_lot_core_0.9.12": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.60\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"libc\",\"req\":\"^0.2.95\",\"target\":\"cfg(unix)\"},{\"name\":\"petgraph\",\"optional\":true,\"req\":\"^0.6.0\"},{\"name\":\"redox_syscall\",\"req\":\"^0.5\",\"target\":\"cfg(target_os = \\\"redox\\\")\"},{\"name\":\"smallvec\",\"req\":\"^1.6.1\"},{\"name\":\"windows-link\",\"req\":\"^0.2.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"deadlock_detection\":[\"petgraph\",\"backtrace\"],\"nightly\":[]}}", + "password-hash_0.4.2": "{\"dependencies\":[{\"name\":\"base64ct\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2\"}],\"features\":{\"alloc\":[\"base64ct/alloc\"],\"default\":[\"rand_core\"],\"std\":[\"alloc\",\"base64ct/std\",\"rand_core/std\"]}}", "paste_1.0.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"paste-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"}],\"features\":{}}", "pastey_0.2.1": "{\"dependencies\":[],\"features\":{}}", "path-absolutize_3.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"path-dedot\",\"req\":\"^3.1.1\"},{\"kind\":\"dev\",\"name\":\"slash-formatter\",\"req\":\"^3\",\"target\":\"cfg(windows)\"}],\"features\":{\"lazy_static_cache\":[\"path-dedot/lazy_static_cache\"],\"once_cell_cache\":[\"path-dedot/once_cell_cache\"],\"unsafe_cache\":[\"path-dedot/unsafe_cache\"],\"use_unix_paths_on_wasm\":[\"path-dedot/use_unix_paths_on_wasm\"]}}", "path-dedot_3.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"lazy_static\",\"optional\":true,\"req\":\"^1.4\"},{\"name\":\"once_cell\",\"req\":\"^1.4\"}],\"features\":{\"lazy_static_cache\":[\"lazy_static\"],\"once_cell_cache\":[],\"unsafe_cache\":[],\"use_unix_paths_on_wasm\":[]}}", "pathdiff_0.2.3": "{\"dependencies\":[{\"name\":\"camino\",\"optional\":true,\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1.0.0\"}],\"features\":{}}", + "pbjson-build_0.6.2": "{\"dependencies\":[{\"name\":\"heck\",\"req\":\"^0.4\"},{\"name\":\"itertools\",\"req\":\"^0.11\"},{\"name\":\"prost\",\"req\":\"^0.12\"},{\"name\":\"prost-types\",\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1\"}],\"features\":{}}", + "pbjson-types_0.6.0": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"chrono\",\"req\":\"^0.4\"},{\"name\":\"pbjson\",\"req\":\"^0.6\"},{\"kind\":\"build\",\"name\":\"pbjson-build\",\"req\":\"^0.6\"},{\"name\":\"prost\",\"req\":\"^0.12\"},{\"kind\":\"build\",\"name\":\"prost-build\",\"req\":\"^0.12\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{}}", + "pbjson_0.6.0": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.21\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"}],\"features\":{}}", + "pbkdf2_0.11.0": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.3\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"sha1\",\"optional\":true,\"package\":\"sha-1\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"package\":\"sha-1\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"simple\"],\"parallel\":[\"rayon\",\"std\"],\"simple\":[\"hmac\",\"password-hash\",\"sha2\"],\"std\":[\"password-hash/std\"]}}", "pbkdf2_0.12.2": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7\"},{\"default_features\":false,\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"hmac\"],\"parallel\":[\"rayon\",\"std\"],\"simple\":[\"hmac\",\"password-hash\",\"sha2\"],\"std\":[\"password-hash/std\"]}}", "pem-rfc7468_0.7.0": "{\"dependencies\":[{\"name\":\"base64ct\",\"req\":\"^1.4\"}],\"features\":{\"alloc\":[\"base64ct/alloc\"],\"std\":[\"alloc\",\"base64ct/std\"]}}", "pem_3.0.6": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"base64\",\"req\":\"^0.22.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"base64/std\",\"serde_core?/std\"]}}", @@ -1129,13 +1157,18 @@ "predicates-tree_1.0.12": "{\"dependencies\":[{\"features\":[\"color\"],\"kind\":\"dev\",\"name\":\"predicates\",\"req\":\"^3.1\"},{\"name\":\"predicates-core\",\"req\":\"^1.0\"},{\"name\":\"termtree\",\"req\":\"^0.5.0\"}],\"features\":{}}", "predicates_3.1.3": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"name\":\"difflib\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"float-cmp\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"normalize-line-endings\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"predicates-core\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"predicates-tree\",\"req\":\"^1.0\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"color\":[],\"default\":[\"diff\",\"regex\",\"float-cmp\",\"normalize-line-endings\",\"color\"],\"diff\":[\"dep:difflib\"],\"unstable\":[]}}", "pretty_assertions_1.4.1": "{\"dependencies\":[{\"name\":\"diff\",\"req\":\"^0.1.12\"},{\"name\":\"yansi\",\"req\":\"^1.0.1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[],\"unstable\":[]}}", + "prettyplease_0.2.37": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"indoc\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.80\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.80\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"default_features\":false,\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.105\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"extra-traits\",\"parsing\",\"printing\",\"visit-mut\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.105\"}],\"features\":{\"verbatim\":[\"syn/parsing\"]}}", "proc-macro-crate_3.4.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.94\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.39\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.99\"},{\"default_features\":false,\"features\":[\"parse\"],\"name\":\"toml_edit\",\"req\":\"^0.23.2\"}],\"features\":{}}", "proc-macro-error-attr2_2.0.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"}],\"features\":{}}", "proc-macro-error2_2.0.1": "{\"dependencies\":[{\"name\":\"proc-macro-error-attr2\",\"req\":\"=2.0.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"syn\",\"optional\":true,\"req\":\"^2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.99\"}],\"features\":{\"default\":[\"syn-error\"],\"nightly\":[],\"syn-error\":[\"dep:syn\"]}}", "proc-macro2_1.0.106": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"proc-macro\"],\"nightly\":[],\"proc-macro\":[],\"span-locations\":[]}}", "process-wrap_9.0.1": "{\"dependencies\":[{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.30\"},{\"name\":\"indexmap\",\"req\":\"^2.9.0\"},{\"default_features\":false,\"features\":[\"fs\",\"poll\",\"signal\"],\"name\":\"nix\",\"optional\":true,\"req\":\"^0.30.1\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"remoteprocess\",\"req\":\"^0.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.20.0\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.38.2\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\",\"rt-multi-thread\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.38.2\"},{\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.40\"},{\"name\":\"windows\",\"optional\":true,\"req\":\"^0.62.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"creation-flags\":[\"dep:windows\",\"windows/Win32_System_Threading\"],\"default\":[\"creation-flags\",\"job-object\",\"kill-on-drop\",\"process-group\",\"process-session\",\"tracing\"],\"job-object\":[\"dep:windows\",\"windows/Win32_Security\",\"windows/Win32_System_Diagnostics_ToolHelp\",\"windows/Win32_System_IO\",\"windows/Win32_System_JobObjects\",\"windows/Win32_System_Threading\"],\"kill-on-drop\":[],\"process-group\":[],\"process-session\":[\"process-group\"],\"reset-sigmask\":[],\"std\":[\"dep:nix\"],\"tokio1\":[\"dep:nix\",\"dep:futures\",\"dep:tokio\"],\"tracing\":[\"dep:tracing\"]}}", "proptest_1.9.0": "{\"dependencies\":[{\"name\":\"bit-set\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"bit-vec\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"bitflags\",\"req\":\"^2.9\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.15\"},{\"name\":\"proptest-macro\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"rand\",\"req\":\"^0.9\"},{\"default_features\":false,\"name\":\"rand_chacha\",\"req\":\"^0.9\"},{\"name\":\"rand_xorshift\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rusty-fork\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"tempfile\",\"optional\":true,\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"=1.0.112\"},{\"name\":\"unarray\",\"req\":\"^0.1.4\"},{\"name\":\"x86\",\"optional\":true,\"req\":\"^0.52.0\"}],\"features\":{\"alloc\":[],\"atomic64bit\":[],\"attr-macro\":[\"proptest-macro\"],\"bit-set\":[\"dep:bit-set\",\"dep:bit-vec\"],\"default\":[\"std\",\"fork\",\"timeout\",\"bit-set\"],\"default-code-coverage\":[\"std\",\"fork\",\"timeout\",\"bit-set\"],\"fork\":[\"std\",\"rusty-fork\",\"tempfile\"],\"handle-panics\":[\"std\"],\"hardware-rng\":[\"x86\"],\"no_std\":[\"num-traits/libm\"],\"std\":[\"rand/std\",\"rand/os_rng\",\"regex-syntax\",\"num-traits/std\"],\"timeout\":[\"fork\",\"rusty-fork/timeout\"],\"unstable\":[]}}", + "prost-build_0.12.6": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"heck\",\"req\":\">=0.4, <=0.5\"},{\"default_features\":false,\"features\":[\"use_alloc\"],\"name\":\"itertools\",\"req\":\">=0.10, <=0.12\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"name\":\"multimap\",\"req\":\">=0.8, <=0.10\"},{\"name\":\"once_cell\",\"req\":\"^1.17.1\"},{\"default_features\":false,\"name\":\"petgraph\",\"req\":\"^0.6\"},{\"name\":\"prettyplease\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"prost\",\"req\":\"^0.12.6\"},{\"default_features\":false,\"name\":\"prost-types\",\"req\":\"^0.12.6\"},{\"default_features\":false,\"name\":\"pulldown-cmark\",\"optional\":true,\"req\":\"^0.9.1\"},{\"name\":\"pulldown-cmark-to-cmark\",\"optional\":true,\"req\":\"^10.0.1\"},{\"default_features\":false,\"features\":[\"std\",\"unicode-bool\"],\"name\":\"regex\",\"req\":\"^1.8.1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"cleanup-markdown\":[\"dep:pulldown-cmark\",\"dep:pulldown-cmark-to-cmark\"],\"default\":[\"format\"],\"format\":[\"dep:prettyplease\",\"dep:syn\"]}}", + "prost-derive_0.12.6": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.1\"},{\"default_features\":false,\"features\":[\"use_alloc\"],\"name\":\"itertools\",\"req\":\">=0.10, <=0.12\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "prost-derive_0.14.3": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.1\"},{\"name\":\"itertools\",\"req\":\">=0.10.1, <=0.14\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", + "prost-types_0.12.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"prost-derive\"],\"name\":\"prost\",\"req\":\"^0.12.6\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"prost/std\"]}}", + "prost_0.12.6": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"prost-derive\",\"optional\":true,\"req\":\"^0.12.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"default\":[\"derive\",\"std\"],\"derive\":[\"dep:prost-derive\"],\"no-recursion-limit\":[],\"prost-derive\":[\"derive\"],\"std\":[]}}", "prost_0.14.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"prost-derive\",\"optional\":true,\"req\":\"^0.14.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"default\":[\"derive\",\"std\"],\"derive\":[\"dep:prost-derive\"],\"no-recursion-limit\":[],\"std\":[]}}", "psl-types_2.0.11": "{\"dependencies\":[],\"features\":{}}", "psl_2.1.184": "{\"dependencies\":[{\"name\":\"psl-types\",\"req\":\"^2.0.11\"},{\"kind\":\"dev\",\"name\":\"rspec\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"helpers\"],\"helpers\":[]}}", @@ -1195,6 +1228,7 @@ "rmcp-macros_0.15.0": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.23\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "rmcp_0.15.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"async-trait\",\"req\":\"^0.1.89\"},{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"serde\",\"clock\",\"std\",\"oldtime\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"serde\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"http\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"reqwest\"],\"name\":\"oauth2\",\"optional\":true,\"req\":\"^5.0\"},{\"name\":\"pastey\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"tokio1\"],\"name\":\"process-wrap\",\"optional\":true,\"req\":\"^9.0\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"json\",\"stream\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"rmcp-macros\",\"optional\":true,\"req\":\"^0.15.0\"},{\"features\":[\"chrono04\"],\"name\":\"schemars\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"chrono04\"],\"kind\":\"dev\",\"name\":\"schemars\",\"req\":\"^1.1.0\"},{\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"sse-stream\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"sync\",\"macros\",\"rt\",\"time\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.4\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"__reqwest\":[\"dep:reqwest\"],\"auth\":[\"dep:oauth2\",\"__reqwest\",\"dep:url\"],\"client\":[\"dep:tokio-stream\"],\"client-side-sse\":[\"dep:sse-stream\",\"dep:http\"],\"default\":[\"base64\",\"macros\",\"server\"],\"elicitation\":[\"dep:url\"],\"macros\":[\"dep:rmcp-macros\",\"dep:pastey\"],\"reqwest\":[\"__reqwest\",\"reqwest?/rustls-tls\"],\"reqwest-native-tls\":[\"__reqwest\",\"reqwest?/native-tls\"],\"reqwest-tls-no-provider\":[\"__reqwest\",\"reqwest?/rustls-tls-no-provider\"],\"schemars\":[\"dep:schemars\"],\"server\":[\"transport-async-rw\",\"dep:schemars\",\"dep:pastey\"],\"server-side-http\":[\"uuid\",\"dep:rand\",\"dep:tokio-stream\",\"dep:http\",\"dep:http-body\",\"dep:http-body-util\",\"dep:bytes\",\"dep:sse-stream\",\"dep:axum\",\"tower\"],\"tower\":[\"dep:tower-service\"],\"transport-async-rw\":[\"tokio/io-util\",\"tokio-util/codec\"],\"transport-child-process\":[\"transport-async-rw\",\"tokio/process\",\"dep:process-wrap\"],\"transport-io\":[\"transport-async-rw\",\"tokio/io-std\"],\"transport-streamable-http-client\":[\"client-side-sse\",\"transport-worker\"],\"transport-streamable-http-client-reqwest\":[\"transport-streamable-http-client\",\"__reqwest\"],\"transport-streamable-http-server\":[\"transport-streamable-http-server-session\",\"server-side-http\",\"transport-worker\"],\"transport-streamable-http-server-session\":[\"transport-async-rw\",\"dep:tokio-stream\"],\"transport-worker\":[\"dep:tokio-stream\"]}}", "rsa_0.9.10": "{\"dependencies\":[{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"base64ct\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"const-oid\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"alloc\",\"oid\"],\"name\":\"digest\",\"req\":\"^0.10.5\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.1\"},{\"default_features\":false,\"features\":[\"i128\",\"prime\",\"zeroize\"],\"name\":\"num-bigint\",\"package\":\"num-bigint-dig\",\"req\":\"^0.8.6\"},{\"default_features\":false,\"name\":\"num-integer\",\"req\":\"^0.1.39\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"req\":\"^0.2.9\"},{\"default_features\":false,\"features\":[\"alloc\",\"pkcs8\"],\"name\":\"pkcs1\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"pkcs8\",\"req\":\"^0.10.2\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand_chacha\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.6.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.184\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.89\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10.5\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10.5\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10.6\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10.6\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha3\",\"req\":\"^0.10.7\"},{\"default_features\":false,\"features\":[\"alloc\",\"digest\",\"rand_core\"],\"name\":\"signature\",\"req\":\">2.0, <2.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"spki\",\"req\":\"^0.7.3\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.1.1\"},{\"features\":[\"alloc\"],\"name\":\"zeroize\",\"req\":\"^1.5\"}],\"features\":{\"default\":[\"std\",\"pem\",\"u64_digit\"],\"getrandom\":[\"rand_core/getrandom\"],\"hazmat\":[],\"nightly\":[\"num-bigint/nightly\"],\"pem\":[\"pkcs1/pem\",\"pkcs8/pem\"],\"pkcs5\":[\"pkcs8/encryption\"],\"serde\":[\"dep:serde\",\"num-bigint/serde\"],\"std\":[\"digest/std\",\"pkcs1/std\",\"pkcs8/std\",\"rand_core/std\",\"signature/std\"],\"u64_digit\":[\"num-bigint/u64_digit\"]}}", + "rtrb_0.3.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"crossbeam-utils\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "rust-embed-impl_8.11.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"rust-embed-utils\",\"req\":\"^8.11.0\"},{\"name\":\"shellexpand\",\"optional\":true,\"req\":\"^3\"},{\"default_features\":false,\"features\":[\"derive\",\"parsing\",\"proc-macro\",\"printing\"],\"name\":\"syn\",\"req\":\"^2\"},{\"name\":\"walkdir\",\"req\":\"^2.3.1\"}],\"features\":{\"compression\":[],\"debug-embed\":[],\"deterministic-timestamps\":[],\"include-exclude\":[\"rust-embed-utils/include-exclude\"],\"interpolate-folder-path\":[\"shellexpand\"],\"mime-guess\":[\"rust-embed-utils/mime-guess\"]}}", "rust-embed-utils_8.11.0": "{\"dependencies\":[{\"name\":\"globset\",\"optional\":true,\"req\":\"^0.4.8\"},{\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0.4\"},{\"name\":\"sha2\",\"req\":\"^0.10.5\"},{\"name\":\"walkdir\",\"req\":\"^2.3.1\"}],\"features\":{\"debug-embed\":[],\"include-exclude\":[\"globset\"],\"mime-guess\":[\"mime_guess\"]}}", "rust-embed_8.11.0": "{\"dependencies\":[{\"name\":\"actix-web\",\"optional\":true,\"req\":\"^4\"},{\"default_features\":false,\"features\":[\"http1\",\"tokio\"],\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"hex\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"include-flate\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0.5\"},{\"default_features\":false,\"features\":[\"server\"],\"name\":\"poem\",\"optional\":true,\"req\":\"^1.3.30\"},{\"default_features\":false,\"name\":\"rocket\",\"optional\":true,\"req\":\"^0.5.0-rc.2\"},{\"name\":\"rust-embed-impl\",\"req\":\"^8.9.0\"},{\"name\":\"rust-embed-utils\",\"req\":\"^8.9.0\"},{\"default_features\":false,\"name\":\"salvo\",\"optional\":true,\"req\":\"^0.16\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"default_features\":false,\"name\":\"warp\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"actix\":[\"actix-web\",\"mime_guess\"],\"axum-ex\":[\"axum\",\"tokio\",\"mime_guess\"],\"compression\":[\"rust-embed-impl/compression\",\"include-flate\"],\"debug-embed\":[\"rust-embed-impl/debug-embed\",\"rust-embed-utils/debug-embed\"],\"deterministic-timestamps\":[\"rust-embed-impl/deterministic-timestamps\"],\"include-exclude\":[\"rust-embed-impl/include-exclude\",\"rust-embed-utils/include-exclude\"],\"interpolate-folder-path\":[\"rust-embed-impl/interpolate-folder-path\"],\"mime-guess\":[\"rust-embed-impl/mime-guess\",\"rust-embed-utils/mime-guess\"],\"poem-ex\":[\"poem\",\"tokio\",\"mime_guess\",\"hex\"],\"salvo-ex\":[\"salvo\",\"tokio\",\"mime_guess\",\"hex\"],\"warp-ex\":[\"warp\",\"tokio\",\"mime_guess\"]}}", @@ -1227,6 +1261,7 @@ "schemars_derive_1.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"serde_derive_internals\",\"req\":\"^0.29.1\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"extra-traits\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "scoped-tls_1.0.1": "{\"dependencies\":[],\"features\":{}}", "scopeguard_1.2.0": "{\"dependencies\":[],\"features\":{\"default\":[\"use_std\"],\"use_std\":[]}}", + "scratch_1.0.9": "{\"dependencies\":[],\"features\":{}}", "scrypt_0.11.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"rand_core\"],\"kind\":\"dev\",\"name\":\"password-hash\",\"req\":\"^0.5\"},{\"name\":\"pbkdf2\",\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"salsa20\",\"req\":\"^0.10.2\"},{\"default_features\":false,\"name\":\"sha2\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"simple\",\"std\"],\"simple\":[\"password-hash\"],\"std\":[\"password-hash/std\"]}}", "sdd_3.0.10": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.6\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{}}", "seccompiler_0.5.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.153\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.27\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.9\"}],\"features\":{\"json\":[\"serde\",\"serde_json\"]}}", @@ -1321,8 +1356,10 @@ "sys-locale_0.3.2": "{\"dependencies\":[{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"features\":[\"Window\",\"WorkerGlobalScope\",\"Navigator\",\"WorkerNavigator\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"}],\"features\":{\"js\":[\"js-sys\",\"wasm-bindgen\",\"web-sys\"]}}", "system-configuration-sys_0.6.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2.149\"}],\"features\":{}}", "system-configuration_0.6.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"core-foundation\",\"req\":\"^0.9\"},{\"name\":\"system-configuration-sys\",\"req\":\"^0.6\"}],\"features\":{}}", + "system-deps_7.0.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5\"},{\"features\":[\"targets\"],\"name\":\"cfg-expr\",\"req\":\">=0.17, <0.21\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"pkg-config\",\"req\":\"^0.3.25\"},{\"default_features\":false,\"features\":[\"parse\",\"std\"],\"name\":\"toml\",\"req\":\"^0.9\"},{\"name\":\"version-compare\",\"req\":\"^0.2\"}],\"features\":{}}", "tagptr_0.2.0": "{\"dependencies\":[],\"features\":{}}", "tar_0.4.44": "{\"dependencies\":[{\"name\":\"filetime\",\"req\":\"^0.2.8\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"xattr\",\"optional\":true,\"req\":\"^1.1.3\",\"target\":\"cfg(unix)\"}],\"features\":{\"default\":[\"xattr\"]}}", + "target-lexicon_0.13.3": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"arch_z80\":[],\"arch_zkasm\":[],\"default\":[],\"serde_support\":[\"serde\",\"std\"],\"std\":[]}}", "tempfile_3.24.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.3\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", "term_0.7.0": "{\"dependencies\":[{\"name\":\"dirs-next\",\"req\":\"^2\"},{\"name\":\"rustversion\",\"req\":\"^1\",\"target\":\"cfg(windows)\"},{\"features\":[\"consoleapi\",\"wincon\",\"handleapi\",\"fileapi\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[]}}", "termcolor_1.4.1": "{\"dependencies\":[{\"name\":\"winapi-util\",\"req\":\"^0.1.3\",\"target\":\"cfg(windows)\"}],\"features\":{}}", @@ -1421,6 +1458,7 @@ "uuid_1.20.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"atomic\",\"optional\":true,\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"borsh-derive\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.20.0\"},{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"md-5\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.56\"},{\"default_features\":false,\"name\":\"sha1_smol\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"slog\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.52\"},{\"name\":\"uuid-rng-internal-lib\",\"optional\":true,\"package\":\"uuid-rng-internal\",\"req\":\"^1.20.0\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"default_features\":false,\"features\":[\"msrv\"],\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"features\":[\"derive\"],\"name\":\"zerocopy\",\"optional\":true,\"req\":\"^0.8\"}],\"features\":{\"atomic\":[\"dep:atomic\"],\"borsh\":[\"dep:borsh\",\"dep:borsh-derive\"],\"default\":[\"std\"],\"fast-rng\":[\"rng\",\"dep:rand\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"macro-diagnostics\":[],\"md5\":[\"dep:md-5\"],\"rng\":[\"dep:getrandom\"],\"rng-getrandom\":[\"rng\",\"dep:getrandom\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/getrandom\"],\"rng-rand\":[\"rng\",\"dep:rand\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/rand\"],\"serde\":[\"dep:serde_core\"],\"sha1\":[\"dep:sha1_smol\"],\"std\":[\"wasm-bindgen?/std\",\"js-sys?/std\"],\"v1\":[\"atomic\"],\"v3\":[\"md5\"],\"v4\":[\"rng\"],\"v5\":[\"sha1\"],\"v6\":[\"atomic\"],\"v7\":[\"rng\"],\"v8\":[]}}", "valuable_0.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"valuable-derive\",\"optional\":true,\"req\":\"=0.1.1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"derive\":[\"valuable-derive\"],\"std\":[\"alloc\"]}}", "vcpkg_0.2.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3.7\"}],\"features\":{}}", + "version-compare_0.2.1": "{\"dependencies\":[],\"features\":{}}", "version_check_0.9.5": "{\"dependencies\":[],\"features\":{}}", "vt100_0.16.2": "{\"dependencies\":[{\"name\":\"itoa\",\"req\":\"^1.0.15\"},{\"features\":[\"term\"],\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.30.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.219\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.140\"},{\"kind\":\"dev\",\"name\":\"terminal_size\",\"req\":\"^0.4.2\"},{\"name\":\"unicode-width\",\"req\":\"^0.2.1\"},{\"name\":\"vte\",\"req\":\"^0.15.0\"}],\"features\":{}}", "vte_0.15.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arrayvec\",\"req\":\"^0.7.2\"},{\"default_features\":false,\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.3.3\"},{\"default_features\":false,\"name\":\"cursor-icon\",\"optional\":true,\"req\":\"^1.0.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.7.4\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.160\"}],\"features\":{\"ansi\":[\"log\",\"cursor-icon\",\"bitflags\"],\"default\":[\"std\"],\"serde\":[\"dep:serde\"],\"std\":[\"memchr/std\"]}}", @@ -1553,11 +1591,14 @@ "zerotrie_0.2.3": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"litemap\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"optional\":true,\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\",\"zerovec?/databake\"],\"default\":[],\"litemap\":[\"dep:litemap\",\"alloc\"],\"serde\":[\"dep:serde_core\",\"dep:litemap\",\"alloc\",\"litemap/serde\",\"zerovec?/serde\"],\"yoke\":[\"dep:yoke\"],\"zerofrom\":[\"dep:zerofrom\"]}}", "zerovec-derive_0.11.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.61\"},{\"name\":\"quote\",\"req\":\"^1.0.28\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.21\"}],\"features\":{}}", "zerovec_0.11.5": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"xxhash64\"],\"name\":\"twox-hash\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec-derive\",\"optional\":true,\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"databake\":[\"dep:databake\"],\"derive\":[\"dep:zerovec-derive\"],\"hashmap\":[\"dep:twox-hash\",\"alloc\"],\"serde\":[\"dep:serde\"],\"std\":[],\"yoke\":[\"dep:yoke\"]}}", + "zip_0.6.6": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8.2\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"byteorder\",\"req\":\"^1.4.3\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"crc32fast\",\"req\":\"^1.3.2\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.8\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0.23\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.5\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12.1\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.11.0\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.7\"},{\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.7\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.11.2\"}],\"features\":{\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate\",\"time\",\"zstd\"],\"deflate\":[\"flate2/rust_backend\"],\"deflate-miniz\":[\"flate2/default\"],\"deflate-zlib\":[\"flate2/zlib\"],\"unreserved\":[]}}", "zip_2.4.2": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"req\":\"^1.4.1\",\"target\":\"cfg(fuzzing)\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"=4.4.18\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"crc32fast\",\"req\":\"^1.4\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"name\":\"deflate64\",\"optional\":true,\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"wasm_js\",\"std\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.1\"},{\"features\":[\"wasm_js\",\"std\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3.1\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"lzma-rs\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"memchr\",\"req\":\"^2.7\"},{\"default_features\":false,\"name\":\"nt-time\",\"optional\":true,\"req\":\"^0.10.6\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.15\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.37\"},{\"default_features\":false,\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.37\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5\"},{\"name\":\"xz2\",\"optional\":true,\"req\":\"^0.1.7\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.8\"},{\"name\":\"zopfli\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13\"}],\"features\":{\"_all-features\":[],\"_deflate-any\":[],\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\",\"getrandom\",\"zeroize\"],\"chrono\":[\"chrono/default\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate64\",\"deflate\",\"lzma\",\"time\",\"zstd\",\"xz\"],\"deflate\":[\"flate2/rust_backend\",\"deflate-zopfli\",\"deflate-flate2\"],\"deflate-flate2\":[\"_deflate-any\"],\"deflate-miniz\":[\"deflate\",\"deflate-flate2\"],\"deflate-zlib\":[\"flate2/zlib\",\"deflate-flate2\"],\"deflate-zlib-ng\":[\"flate2/zlib-ng\",\"deflate-flate2\"],\"deflate-zopfli\":[\"zopfli\",\"_deflate-any\"],\"lzma\":[\"lzma-rs/stream\"],\"nt-time\":[\"dep:nt-time\"],\"unreserved\":[],\"xz\":[\"dep:xz2\"]}}", "zmij_1.0.19": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"opt-level\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"ryu\",\"req\":\"^1\"}],\"features\":{}}", "zopfli_0.8.3": "{\"dependencies\":[{\"name\":\"bumpalo\",\"req\":\"^3.19.0\"},{\"default_features\":false,\"name\":\"crc32fast\",\"optional\":true,\"req\":\"^1.5.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.28\"},{\"kind\":\"dev\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.9\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"proptest-derive\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"simd-adler32\",\"optional\":true,\"req\":\"^0.3.7\"}],\"features\":{\"default\":[\"gzip\",\"std\",\"zlib\"],\"gzip\":[\"dep:crc32fast\"],\"nightly\":[\"crc32fast?/nightly\"],\"std\":[\"crc32fast?/std\",\"dep:log\",\"simd-adler32?/std\"],\"zlib\":[\"dep:simd-adler32\"]}}", + "zstd-safe_5.0.2+zstd.1.5.2": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.21\"},{\"default_features\":false,\"name\":\"zstd-sys\",\"req\":\"^2.0.1\"}],\"features\":{\"arrays\":[],\"bindgen\":[\"zstd-sys/bindgen\"],\"debug\":[\"zstd-sys/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-sys/experimental\"],\"legacy\":[\"zstd-sys/legacy\"],\"no_asm\":[\"zstd-sys/no_asm\"],\"pkg-config\":[\"zstd-sys/pkg-config\"],\"std\":[\"zstd-sys/std\"],\"thin\":[\"zstd-sys/thin\"],\"zdict_builder\":[\"zstd-sys/zdict_builder\"],\"zstdmt\":[\"zstd-sys/zstdmt\"]}}", "zstd-safe_7.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"zstd-sys\",\"req\":\"^2.0.15\"}],\"features\":{\"arrays\":[],\"bindgen\":[\"zstd-sys/bindgen\"],\"debug\":[\"zstd-sys/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-sys/experimental\"],\"fat-lto\":[\"zstd-sys/fat-lto\"],\"legacy\":[\"zstd-sys/legacy\"],\"no_asm\":[\"zstd-sys/no_asm\"],\"pkg-config\":[\"zstd-sys/pkg-config\"],\"seekable\":[\"zstd-sys/seekable\"],\"std\":[\"zstd-sys/std\"],\"thin\":[\"zstd-sys/thin\"],\"thin-lto\":[\"zstd-sys/thin-lto\"],\"zdict_builder\":[\"zstd-sys/zdict_builder\"],\"zstdmt\":[\"zstd-sys/zstdmt\"]}}", "zstd-sys_2.0.16+zstd.1.5.7": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72\"},{\"features\":[\"parallel\"],\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.45\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.28\"}],\"features\":{\"debug\":[],\"default\":[\"legacy\",\"zdict_builder\",\"bindgen\"],\"experimental\":[],\"fat-lto\":[],\"legacy\":[],\"no_asm\":[],\"no_wasm_shim\":[],\"non-cargo\":[],\"pkg-config\":[],\"seekable\":[],\"std\":[],\"thin\":[],\"thin-lto\":[],\"zdict_builder\":[],\"zstdmt\":[]}}", + "zstd_0.11.2+zstd.1.5.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"humansize\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"zstd-safe\",\"req\":\"^5.0.1\"}],\"features\":{\"arrays\":[\"zstd-safe/arrays\"],\"bindgen\":[\"zstd-safe/bindgen\"],\"debug\":[\"zstd-safe/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-safe/experimental\"],\"legacy\":[\"zstd-safe/legacy\"],\"no_asm\":[\"zstd-safe/no_asm\"],\"pkg-config\":[\"zstd-safe/pkg-config\"],\"thin\":[\"zstd-safe/thin\"],\"wasm\":[],\"zdict_builder\":[\"zstd-safe/zdict_builder\"],\"zstdmt\":[\"zstd-safe/zstdmt\"]}}", "zstd_0.13.3": "{\"dependencies\":[{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.0\"},{\"kind\":\"dev\",\"name\":\"humansize\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"zstd-safe\",\"req\":\"^7.1.0\"}],\"features\":{\"arrays\":[\"zstd-safe/arrays\"],\"bindgen\":[\"zstd-safe/bindgen\"],\"debug\":[\"zstd-safe/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-safe/experimental\"],\"fat-lto\":[\"zstd-safe/fat-lto\"],\"legacy\":[\"zstd-safe/legacy\"],\"no_asm\":[\"zstd-safe/no_asm\"],\"pkg-config\":[\"zstd-safe/pkg-config\"],\"thin\":[\"zstd-safe/thin\"],\"thin-lto\":[\"zstd-safe/thin-lto\"],\"wasm\":[],\"zdict_builder\":[\"zstd-safe/zdict_builder\"],\"zstdmt\":[\"zstd-safe/zstdmt\"]}}", "zune-core_0.4.12": "{\"dependencies\":[{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.52\"}],\"features\":{\"std\":[]}}", "zune-core_0.5.1": "{\"dependencies\":[{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"std\":[]}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 64abfaac3c..b8f36d67df 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1099,6 +1099,16 @@ dependencies = [ "bytes", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + [[package]] name = "bzip2" version = "0.5.2" @@ -1202,6 +1212,16 @@ dependencies = [ "nom 7.1.3", ] +[[package]] +name = "cfg-expr" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -1335,7 +1355,7 @@ version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -1371,6 +1391,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9b18233253483ce2f65329a24072ec414db782531bdbb7d0bbc4bd2ce6b7e21" +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.1", +] + [[package]] name = "codex-ansi-escape" version = "0.0.0" @@ -1394,6 +1425,7 @@ dependencies = [ "eventsource-stream", "futures", "http 1.4.0", + "libwebrtc", "pretty_assertions", "regex-lite", "reqwest", @@ -1574,7 +1606,7 @@ dependencies = [ "url", "which", "wiremock", - "zip", + "zip 2.4.2", ] [[package]] @@ -1694,7 +1726,7 @@ dependencies = [ "tracing", "tracing-opentelemetry", "tracing-subscriber", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -1898,8 +1930,8 @@ dependencies = [ "wildmatch", "windows-sys 0.52.0", "wiremock", - "zip", - "zstd", + "zip 2.4.2", + "zstd 0.13.3", ] [[package]] @@ -2259,7 +2291,7 @@ dependencies = [ "tokio", "url", "wiremock", - "zip", + "zip 2.4.2", ] [[package]] @@ -2857,6 +2889,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -2940,7 +2978,7 @@ dependencies = [ "tokio-tungstenite", "walkdir", "wiremock", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -3182,6 +3220,68 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "cxx" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e" +dependencies = [ + "cc", + "cxx-build", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash 0.2.0", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" +dependencies = [ + "cc", + "codespan-reporting", + "indexmap 2.13.0", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.114", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" +dependencies = [ + "clap", + "codespan-reporting", + "indexmap 2.13.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.194" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" +dependencies = [ + "indexmap 2.13.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "darling" version = "0.20.11" @@ -3702,7 +3802,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -4104,6 +4204,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -4328,6 +4438,63 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "gio-sys" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.61.2", +] + +[[package]] +name = "glib" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" +dependencies = [ + "heck 0.5.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "glib-sys" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glob" version = "0.3.3" @@ -4347,6 +4514,17 @@ dependencies = [ "regex-syntax 0.8.8", ] +[[package]] +name = "gobject-sys" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "h2" version = "0.4.13" @@ -4448,6 +4626,12 @@ dependencies = [ "http 1.4.0", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -5214,6 +5398,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -5452,6 +5645,31 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libwebrtc" +version = "0.3.26" +source = "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0" +dependencies = [ + "cxx", + "glib", + "jni", + "js-sys", + "lazy_static", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "rtrb", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webrtc-sys", +] + [[package]] name = "libz-sys" version = "1.1.23" @@ -5463,6 +5681,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -5497,6 +5724,31 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "livekit-protocol" +version = "0.7.1" +source = "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0" +dependencies = [ + "futures-util", + "livekit-runtime", + "parking_lot", + "pbjson", + "pbjson-types", + "prost 0.12.6", + "serde", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "livekit-runtime" +version = "0.4.0" +source = "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0" +dependencies = [ + "tokio", + "tokio-stream", +] + [[package]] name = "local-waker" version = "0.1.4" @@ -6502,7 +6754,7 @@ dependencies = [ "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", - "prost", + "prost 0.14.3", "reqwest", "serde_json", "thiserror 2.0.18", @@ -6521,7 +6773,7 @@ dependencies = [ "const-hex", "opentelemetry", "opentelemetry_sdk", - "prost", + "prost 0.14.3", "serde", "serde_json", "tonic", @@ -6632,6 +6884,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -6668,6 +6931,55 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck 0.4.1", + "itertools 0.11.0", + "prost 0.12.6", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost 0.12.6", + "prost-build", + "serde", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -6962,6 +7274,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -7031,6 +7353,16 @@ dependencies = [ "unarray", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + [[package]] name = "prost" version = "0.14.3" @@ -7038,7 +7370,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.14.3", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.11.0", + "log", + "multimap", + "once_cell", + "petgraph 0.6.5", + "prettyplease", + "prost 0.12.6", + "prost-types", + "regex", + "syn 2.0.114", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -7054,6 +7420,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + [[package]] name = "psl" version = "2.1.184" @@ -7883,6 +8258,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rtrb" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7204ed6420f698836b76d4d5c2ec5dec7585fd5c3a788fd1cde855d1de598239" + [[package]] name = "runfiles" version = "0.1.0" @@ -8230,13 +8611,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + [[package]] name = "scrypt" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "pbkdf2", + "pbkdf2 0.12.2", "salsa20", "sha2", ] @@ -8921,7 +9308,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -9236,7 +9623,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", @@ -9249,7 +9636,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -9373,6 +9760,19 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "7.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.9.11+spec-1.1.0", + "version-compare", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -9390,6 +9790,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + [[package]] name = "tempfile" version = "3.24.0" @@ -9879,7 +10285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" dependencies = [ "bytes", - "prost", + "prost 0.14.3", "tonic", ] @@ -10421,6 +10827,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" @@ -10702,6 +11114,34 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webrtc-sys" +version = "0.3.24" +source = "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0" +dependencies = [ + "cc", + "cxx", + "cxx-build", + "glob", + "log", + "pkg-config", + "webrtc-sys-build", +] + +[[package]] +name = "webrtc-sys-build" +version = "0.3.13" +source = "git+https://github.com/juberti-oai/rust-sdks.git?rev=e2d1d1d230c6fc9df171ccb181423f957bb3c1f0#e2d1d1d230c6fc9df171ccb181423f957bb3c1f0" +dependencies = [ + "anyhow", + "fs2", + "regex", + "reqwest", + "scratch", + "semver", + "zip 0.6.6", +] + [[package]] name = "weezl" version = "0.1.12" @@ -11675,6 +12115,26 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2 0.4.4", + "constant_time_eq 0.1.5", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd 0.11.2+zstd.1.5.2", +] + [[package]] name = "zip" version = "2.4.2" @@ -11683,8 +12143,8 @@ checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", "arbitrary", - "bzip2", - "constant_time_eq", + "bzip2 0.5.2", + "constant_time_eq 0.3.1", "crc32fast", "crossbeam-utils", "deflate64", @@ -11695,14 +12155,14 @@ dependencies = [ "indexmap 2.13.0", "lzma-rs", "memchr", - "pbkdf2", + "pbkdf2 0.12.2", "sha1", "thiserror 2.0.18", "time", "xz2", "zeroize", "zopfli", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -11723,13 +12183,32 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + [[package]] name = "zstd" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zstd-safe", + "zstd-safe 7.2.4", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", ] [[package]] diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 681487c099..3c40bf3cb8 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -382,6 +382,8 @@ crossterm = { git = "https://github.com/nornagon/crossterm", branch = "nornagon/ ratatui = { git = "https://github.com/nornagon/ratatui", branch = "nornagon-v0.29.0-patch" } tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungstenite", rev = "132f5b39c862e3a970f731d709608b3e6276d5f6" } tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "9200079d3b54a1ff51072e24d81fd354f085156f" } +libwebrtc = { git = "https://github.com/juberti-oai/rust-sdks.git", rev = "e2d1d1d230c6fc9df171ccb181423f957bb3c1f0" } +webrtc-sys = { git = "https://github.com/juberti-oai/rust-sdks.git", rev = "e2d1d1d230c6fc9df171ccb181423f957bb3c1f0" } # Uncomment to debug local changes. # rmcp = { path = "../../rust-sdk/crates/rmcp" } diff --git a/codex-rs/app-server/build.rs b/codex-rs/app-server/build.rs new file mode 100644 index 0000000000..abccc48bb6 --- /dev/null +++ b/codex-rs/app-server/build.rs @@ -0,0 +1,5 @@ +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { + println!("cargo:rustc-link-arg=-ObjC"); + } +} diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 20e9758796..27401041a4 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -21,7 +21,6 @@ mod plugin_install; mod plugin_list; mod plugin_uninstall; mod rate_limits; -mod realtime_conversation; mod request_permissions; mod request_user_input; mod review; diff --git a/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs b/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs deleted file mode 100644 index dafdd9eb88..0000000000 --- a/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs +++ /dev/null @@ -1,424 +0,0 @@ -use anyhow::Context; -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_mock_responses_server_sequence_unchecked; -use app_test_support::to_response; -use codex_app_server_protocol::JSONRPCError; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginAccountResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::ThreadRealtimeAppendAudioParams; -use codex_app_server_protocol::ThreadRealtimeAppendAudioResponse; -use codex_app_server_protocol::ThreadRealtimeAppendTextParams; -use codex_app_server_protocol::ThreadRealtimeAppendTextResponse; -use codex_app_server_protocol::ThreadRealtimeAudioChunk; -use codex_app_server_protocol::ThreadRealtimeClosedNotification; -use codex_app_server_protocol::ThreadRealtimeErrorNotification; -use codex_app_server_protocol::ThreadRealtimeItemAddedNotification; -use codex_app_server_protocol::ThreadRealtimeOutputAudioDeltaNotification; -use codex_app_server_protocol::ThreadRealtimeStartParams; -use codex_app_server_protocol::ThreadRealtimeStartResponse; -use codex_app_server_protocol::ThreadRealtimeStartedNotification; -use codex_app_server_protocol::ThreadRealtimeStopParams; -use codex_app_server_protocol::ThreadRealtimeStopResponse; -use codex_app_server_protocol::ThreadStartParams; -use codex_app_server_protocol::ThreadStartResponse; -use codex_core::features::FEATURES; -use codex_core::features::Feature; -use core_test_support::responses::start_websocket_server; -use core_test_support::skip_if_no_network; -use pretty_assertions::assert_eq; -use serde::de::DeserializeOwned; -use serde_json::json; -use std::path::Path; -use std::time::Duration; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); -const STARTUP_CONTEXT_HEADER: &str = "Startup context from Codex."; - -#[tokio::test] -async fn realtime_conversation_streams_v2_notifications() -> Result<()> { - skip_if_no_network!(Ok(())); - - let responses_server = create_mock_responses_server_sequence_unchecked(Vec::new()).await; - let realtime_server = start_websocket_server(vec![vec![ - vec![json!({ - "type": "session.updated", - "session": { "id": "sess_backend", "instructions": "backend prompt" } - })], - vec![], - vec![ - json!({ - "type": "response.output_audio.delta", - "delta": "AQID", - "sample_rate": 24_000, - "channels": 1, - "samples_per_channel": 512 - }), - json!({ - "type": "conversation.item.added", - "item": { - "type": "message", - "role": "assistant", - "content": [{ "type": "text", "text": "hi" }] - } - }), - json!({ - "type": "error", - "message": "upstream boom" - }), - ], - ]]) - .await; - - let codex_home = TempDir::new()?; - create_config_toml( - codex_home.path(), - &responses_server.uri(), - realtime_server.uri(), - true, - )?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - mcp.initialize().await?; - login_with_api_key(&mut mcp, "sk-test-key").await?; - - let thread_start_request_id = mcp - .send_thread_start_request(ThreadStartParams::default()) - .await?; - let thread_start_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(thread_start_request_id)), - ) - .await??; - let thread_start: ThreadStartResponse = to_response(thread_start_response)?; - - let start_request_id = mcp - .send_thread_realtime_start_request(ThreadRealtimeStartParams { - thread_id: thread_start.thread.id.clone(), - prompt: "backend prompt".to_string(), - session_id: None, - }) - .await?; - let start_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(start_request_id)), - ) - .await??; - let _: ThreadRealtimeStartResponse = to_response(start_response)?; - - let started = - read_notification::(&mut mcp, "thread/realtime/started") - .await?; - assert_eq!(started.thread_id, thread_start.thread.id); - assert!(started.session_id.is_some()); - - let startup_context_request = realtime_server.wait_for_request(0, 0).await; - assert_eq!( - startup_context_request.body_json()["type"].as_str(), - Some("session.update") - ); - assert!( - startup_context_request.body_json()["session"]["instructions"] - .as_str() - .context("expected startup context instructions")? - .contains(STARTUP_CONTEXT_HEADER) - ); - - let audio_append_request_id = mcp - .send_thread_realtime_append_audio_request(ThreadRealtimeAppendAudioParams { - thread_id: started.thread_id.clone(), - audio: ThreadRealtimeAudioChunk { - data: "BQYH".to_string(), - sample_rate: 24_000, - num_channels: 1, - samples_per_channel: Some(480), - }, - }) - .await?; - let audio_append_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(audio_append_request_id)), - ) - .await??; - let _: ThreadRealtimeAppendAudioResponse = to_response(audio_append_response)?; - - let text_append_request_id = mcp - .send_thread_realtime_append_text_request(ThreadRealtimeAppendTextParams { - thread_id: started.thread_id.clone(), - text: "hello".to_string(), - }) - .await?; - let text_append_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(text_append_request_id)), - ) - .await??; - let _: ThreadRealtimeAppendTextResponse = to_response(text_append_response)?; - - let output_audio = read_notification::( - &mut mcp, - "thread/realtime/outputAudio/delta", - ) - .await?; - assert_eq!(output_audio.audio.data, "AQID"); - assert_eq!(output_audio.audio.sample_rate, 24_000); - assert_eq!(output_audio.audio.num_channels, 1); - assert_eq!(output_audio.audio.samples_per_channel, Some(512)); - - let item_added = read_notification::( - &mut mcp, - "thread/realtime/itemAdded", - ) - .await?; - assert_eq!(item_added.thread_id, output_audio.thread_id); - assert_eq!(item_added.item["type"], json!("message")); - - let realtime_error = - read_notification::(&mut mcp, "thread/realtime/error") - .await?; - assert_eq!(realtime_error.thread_id, output_audio.thread_id); - assert_eq!(realtime_error.message, "upstream boom"); - - let closed = - read_notification::(&mut mcp, "thread/realtime/closed") - .await?; - assert_eq!(closed.thread_id, output_audio.thread_id); - assert_eq!(closed.reason.as_deref(), Some("transport_closed")); - - let connections = realtime_server.connections(); - assert_eq!(connections.len(), 1); - let connection = &connections[0]; - assert_eq!(connection.len(), 3); - assert_eq!( - connection[0].body_json()["type"].as_str(), - Some("session.update") - ); - assert!( - connection[0].body_json()["session"]["instructions"] - .as_str() - .context("expected startup context instructions")? - .contains(STARTUP_CONTEXT_HEADER) - ); - let mut request_types = [ - connection[1].body_json()["type"] - .as_str() - .context("expected websocket request type")? - .to_string(), - connection[2].body_json()["type"] - .as_str() - .context("expected websocket request type")? - .to_string(), - ]; - request_types.sort(); - assert_eq!( - request_types, - [ - "conversation.item.create".to_string(), - "input_audio_buffer.append".to_string(), - ] - ); - - realtime_server.shutdown().await; - Ok(()) -} - -#[tokio::test] -async fn realtime_conversation_stop_emits_closed_notification() -> Result<()> { - skip_if_no_network!(Ok(())); - - let responses_server = create_mock_responses_server_sequence_unchecked(Vec::new()).await; - let realtime_server = start_websocket_server(vec![vec![ - vec![json!({ - "type": "session.updated", - "session": { "id": "sess_backend", "instructions": "backend prompt" } - })], - vec![], - ]]) - .await; - - let codex_home = TempDir::new()?; - create_config_toml( - codex_home.path(), - &responses_server.uri(), - realtime_server.uri(), - true, - )?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - mcp.initialize().await?; - login_with_api_key(&mut mcp, "sk-test-key").await?; - - let thread_start_request_id = mcp - .send_thread_start_request(ThreadStartParams::default()) - .await?; - let thread_start_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(thread_start_request_id)), - ) - .await??; - let thread_start: ThreadStartResponse = to_response(thread_start_response)?; - - let start_request_id = mcp - .send_thread_realtime_start_request(ThreadRealtimeStartParams { - thread_id: thread_start.thread.id.clone(), - prompt: "backend prompt".to_string(), - session_id: None, - }) - .await?; - let start_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(start_request_id)), - ) - .await??; - let _: ThreadRealtimeStartResponse = to_response(start_response)?; - - let started = - read_notification::(&mut mcp, "thread/realtime/started") - .await?; - - let stop_request_id = mcp - .send_thread_realtime_stop_request(ThreadRealtimeStopParams { - thread_id: started.thread_id.clone(), - }) - .await?; - let stop_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(stop_request_id)), - ) - .await??; - let _: ThreadRealtimeStopResponse = to_response(stop_response)?; - - let closed = - read_notification::(&mut mcp, "thread/realtime/closed") - .await?; - assert_eq!(closed.thread_id, started.thread_id); - assert!(matches!( - closed.reason.as_deref(), - Some("requested" | "transport_closed") - )); - - realtime_server.shutdown().await; - Ok(()) -} - -#[tokio::test] -async fn realtime_conversation_requires_feature_flag() -> Result<()> { - skip_if_no_network!(Ok(())); - - let responses_server = create_mock_responses_server_sequence_unchecked(Vec::new()).await; - let realtime_server = start_websocket_server(vec![vec![]]).await; - - let codex_home = TempDir::new()?; - create_config_toml( - codex_home.path(), - &responses_server.uri(), - realtime_server.uri(), - false, - )?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - mcp.initialize().await?; - - let thread_start_request_id = mcp - .send_thread_start_request(ThreadStartParams::default()) - .await?; - let thread_start_response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(thread_start_request_id)), - ) - .await??; - let thread_start: ThreadStartResponse = to_response(thread_start_response)?; - - let start_request_id = mcp - .send_thread_realtime_start_request(ThreadRealtimeStartParams { - thread_id: thread_start.thread.id.clone(), - prompt: "backend prompt".to_string(), - session_id: None, - }) - .await?; - let error = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_error_message(RequestId::Integer(start_request_id)), - ) - .await??; - assert_invalid_request( - error, - format!( - "thread {} does not support realtime conversation", - thread_start.thread.id - ), - ); - - realtime_server.shutdown().await; - Ok(()) -} - -async fn read_notification(mcp: &mut McpProcess, method: &str) -> Result { - let notification = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_notification_message(method), - ) - .await??; - let params = notification - .params - .context("expected notification params to be present")?; - Ok(serde_json::from_value(params)?) -} - -async fn login_with_api_key(mcp: &mut McpProcess, api_key: &str) -> Result<()> { - let request_id = mcp.send_login_account_api_key_request(api_key).await?; - let response: JSONRPCResponse = timeout( - DEFAULT_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - let login: LoginAccountResponse = to_response(response)?; - assert_eq!(login, LoginAccountResponse::ApiKey {}); - - Ok(()) -} - -fn create_config_toml( - codex_home: &Path, - responses_server_uri: &str, - realtime_server_uri: &str, - realtime_enabled: bool, -) -> std::io::Result<()> { - let realtime_feature_key = FEATURES - .iter() - .find(|spec| spec.id == Feature::RealtimeConversation) - .map(|spec| spec.key) - .unwrap_or("realtime_conversation"); - - std::fs::write( - codex_home.join("config.toml"), - format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "read-only" -model_provider = "mock_provider" -experimental_realtime_ws_base_url = "{realtime_server_uri}" - -[features] -{realtime_feature_key} = {realtime_enabled} - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{responses_server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ), - ) -} - -fn assert_invalid_request(error: JSONRPCError, message: String) { - assert_eq!(error.error.code, -32600); - assert_eq!(error.error.message, message); - assert_eq!(error.error.data, None); -} diff --git a/codex-rs/cli/build.rs b/codex-rs/cli/build.rs new file mode 100644 index 0000000000..abccc48bb6 --- /dev/null +++ b/codex-rs/cli/build.rs @@ -0,0 +1,5 @@ +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { + println!("cargo:rustc-link-arg=-ObjC"); + } +} diff --git a/codex-rs/codex-api/Cargo.toml b/codex-rs/codex-api/Cargo.toml index a3a5fa4d92..130e83ccb1 100644 --- a/codex-rs/codex-api/Cargo.toml +++ b/codex-rs/codex-api/Cargo.toml @@ -20,7 +20,9 @@ tokio-tungstenite = { workspace = true } tungstenite = { workspace = true } tracing = { workspace = true } eventsource-stream = { workspace = true } +libwebrtc = "0.3.26" regex-lite = { workspace = true } +reqwest = { workspace = true, features = ["json", "multipart"] } tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true } @@ -30,7 +32,6 @@ assert_matches = { workspace = true } pretty_assertions = { workspace = true } tokio-test = { workspace = true } wiremock = { workspace = true } -reqwest = { workspace = true } [lints] workspace = true diff --git a/codex-rs/codex-api/build.rs b/codex-rs/codex-api/build.rs new file mode 100644 index 0000000000..abccc48bb6 --- /dev/null +++ b/codex-rs/codex-api/build.rs @@ -0,0 +1,5 @@ +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { + println!("cargo:rustc-link-arg=-ObjC"); + } +} diff --git a/codex-rs/codex-api/src/endpoint/mod.rs b/codex-rs/codex-api/src/endpoint/mod.rs index 6a748e533d..a7803f9911 100644 --- a/codex-rs/codex-api/src/endpoint/mod.rs +++ b/codex-rs/codex-api/src/endpoint/mod.rs @@ -1,7 +1,8 @@ pub mod compact; pub mod memories; pub mod models; -pub mod realtime_websocket; +pub mod realtime_webrtc; +mod realtime_websocket; pub mod responses; pub mod responses_websocket; mod session; diff --git a/codex-rs/codex-api/src/endpoint/realtime_webrtc/mod.rs b/codex-rs/codex-api/src/endpoint/realtime_webrtc/mod.rs new file mode 100644 index 0000000000..3546c7759c --- /dev/null +++ b/codex-rs/codex-api/src/endpoint/realtime_webrtc/mod.rs @@ -0,0 +1,544 @@ +use crate::endpoint::realtime_websocket::parse_realtime_event; +use crate::error::ApiError; +use crate::provider::Provider; +use codex_protocol::protocol::RealtimeEvent; +use http::HeaderMap; +use libwebrtc::MediaType; +use libwebrtc::data_channel::DataChannel; +use libwebrtc::data_channel::DataChannelInit; +use libwebrtc::data_channel::DataChannelState; +use libwebrtc::peer_connection::OfferOptions; +use libwebrtc::peer_connection::PeerConnection; +use libwebrtc::peer_connection_factory::PeerConnectionFactory; +use libwebrtc::peer_connection_factory::RtcConfiguration; +use libwebrtc::peer_connection_factory::native::PeerConnectionFactoryExt; +use libwebrtc::rtp_transceiver::RtpTransceiverDirection; +use libwebrtc::rtp_transceiver::RtpTransceiverInit; +use libwebrtc::session_description::SdpType; +use libwebrtc::session_description::SessionDescription; +use reqwest::Client; +use reqwest::multipart::Form; +use serde_json::json; +use std::sync::Arc; +use std::sync::Mutex as StdMutex; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use tokio::sync::Mutex; +use tokio::sync::mpsc; +use tokio::sync::oneshot; +use tokio::time::Duration; +use tracing::debug; +use tracing::info; +use tracing::warn; +use url::Url; + +const REALTIME_CALLS_PATH: &str = "/v1/realtime/calls"; +const REALTIME_DATA_CHANNEL_LABEL: &str = "oai-events"; +const REALTIME_VOICE: &str = "marin"; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RealtimeSessionConfig { + pub instructions: String, + pub model: Option, + pub session_id: Option, +} + +pub struct RealtimeWebrtcConnection { + writer: RealtimeWebrtcWriter, + events: RealtimeWebrtcEvents, +} + +#[derive(Clone)] +pub struct RealtimeWebrtcWriter { + peer_connection: PeerConnection, + data_channel: DataChannel, + is_closed: Arc, +} + +#[derive(Clone)] +pub struct RealtimeWebrtcEvents { + rx_event: Arc>>, + is_closed: Arc, +} + +pub struct RealtimeWebrtcClient { + provider: Provider, +} + +impl RealtimeWebrtcConnection { + pub async fn send_conversation_item_create(&self, text: String) -> Result<(), ApiError> { + self.writer.send_conversation_item_create(text).await + } + + pub async fn send_conversation_handoff_append( + &self, + handoff_id: String, + output_text: String, + ) -> Result<(), ApiError> { + self.writer + .send_conversation_handoff_append(handoff_id, output_text) + .await + } + + pub async fn send_function_call_output( + &self, + call_id: String, + output_text: String, + ) -> Result<(), ApiError> { + self.writer + .send_function_call_output(call_id, output_text) + .await + } + + pub async fn send_response_create(&self) -> Result<(), ApiError> { + self.writer.send_response_create().await + } + + pub async fn close(&self) -> Result<(), ApiError> { + self.writer.close().await + } + + pub async fn next_event(&self) -> Result, ApiError> { + self.events.next_event().await + } + + pub fn writer(&self) -> RealtimeWebrtcWriter { + self.writer.clone() + } + + pub fn events(&self) -> RealtimeWebrtcEvents { + self.events.clone() + } + + fn new( + peer_connection: PeerConnection, + data_channel: DataChannel, + rx_event: mpsc::UnboundedReceiver, + is_closed: Arc, + ) -> Self { + Self { + writer: RealtimeWebrtcWriter { + peer_connection, + data_channel, + is_closed: Arc::clone(&is_closed), + }, + events: RealtimeWebrtcEvents { + rx_event: Arc::new(Mutex::new(rx_event)), + is_closed, + }, + } + } +} + +impl RealtimeWebrtcWriter { + pub async fn send_conversation_item_create(&self, text: String) -> Result<(), ApiError> { + self.send_json(json!({ + "type": "conversation.item.create", + "item": { + "type": "message", + "role": "user", + "content": [{ + "type": "input_text", + "text": text, + }], + }, + })) + .await + } + + pub async fn send_conversation_handoff_append( + &self, + _handoff_id: String, + output_text: String, + ) -> Result<(), ApiError> { + self.send_json(json!({ + "type": "conversation.item.create", + "item": { + "type": "message", + "role": "assistant", + "content": [{ + "type": "output_text", + "text": output_text, + }], + }, + })) + .await + } + + pub async fn send_function_call_output( + &self, + call_id: String, + output_text: String, + ) -> Result<(), ApiError> { + self.send_json(json!({ + "type": "conversation.item.create", + "item": { + "type": "function_call_output", + "call_id": call_id, + "output": json!({ + "content": output_text, + }).to_string(), + }, + })) + .await + } + + pub async fn send_response_create(&self) -> Result<(), ApiError> { + self.send_json(json!({ "type": "response.create" })).await + } + + pub async fn close(&self) -> Result<(), ApiError> { + if self.is_closed.swap(true, Ordering::SeqCst) { + return Ok(()); + } + self.data_channel.close(); + self.peer_connection.close(); + Ok(()) + } + + async fn send_json(&self, payload: serde_json::Value) -> Result<(), ApiError> { + if self.is_closed.load(Ordering::SeqCst) { + return Err(ApiError::Stream( + "realtime WebRTC connection is closed".to_string(), + )); + } + + let serialized = serde_json::to_vec(&payload).map_err(|err| { + ApiError::Stream(format!("failed to serialize realtime event: {err}")) + })?; + self.data_channel.send(&serialized, false).map_err(|err| { + ApiError::Stream(format!("failed to send realtime data channel event: {err}")) + }) + } +} + +impl RealtimeWebrtcEvents { + pub async fn next_event(&self) -> Result, ApiError> { + if self.is_closed.load(Ordering::SeqCst) { + return Ok(None); + } + + match self.rx_event.lock().await.recv().await { + Some(event) => Ok(Some(event)), + None => { + self.is_closed.store(true, Ordering::SeqCst); + Ok(None) + } + } + } +} + +impl RealtimeWebrtcClient { + pub fn new(provider: Provider) -> Self { + Self { provider } + } + + pub async fn connect( + &self, + config: RealtimeSessionConfig, + extra_headers: HeaderMap, + default_headers: HeaderMap, + ) -> Result { + info!("initializing realtime WebRTC peer connection"); + let factory = PeerConnectionFactory::with_platform_adm(); + let peer_connection = factory + .create_peer_connection(RtcConfiguration::default()) + .map_err(|err| { + ApiError::Stream(format!("failed to create WebRTC peer connection: {err}")) + })?; + + // Negotiate an audio m-line and attach a local mic track backed by the platform ADM. + let audio_transceiver = peer_connection + .add_transceiver_for_media( + MediaType::Audio, + RtpTransceiverInit { + direction: RtpTransceiverDirection::SendRecv, + stream_ids: vec!["realtime".to_string()], + send_encodings: Vec::new(), + }, + ) + .map_err(|err| ApiError::Stream(format!("failed to add audio transceiver: {err}")))?; + + let local_audio_source = factory.create_audio_source(); + let local_audio_track = factory.create_audio_track("realtime-mic", local_audio_source); + audio_transceiver + .sender() + .set_track(Some(local_audio_track.into())) + .map_err(|err| { + ApiError::Stream(format!("failed to attach ADM audio track to sender: {err}")) + })?; + + let data_channel = peer_connection + .create_data_channel(REALTIME_DATA_CHANNEL_LABEL, DataChannelInit::default()) + .map_err(|err| { + ApiError::Stream(format!("failed to create realtime data channel: {err}")) + })?; + + let (tx_event, rx_event) = mpsc::unbounded_channel(); + let is_closed = Arc::new(AtomicBool::new(false)); + let (tx_open, rx_open) = oneshot::channel::<()>(); + let tx_open = Arc::new(StdMutex::new(Some(tx_open))); + + { + let tx_event = tx_event.clone(); + data_channel.on_message(Some(Box::new(move |buffer| { + if buffer.binary { + debug!( + payload_len = buffer.data.len(), + "ignoring binary realtime data channel message" + ); + return; + } + + let payload = match std::str::from_utf8(buffer.data) { + Ok(payload) => payload, + Err(err) => { + debug!("received non-utf8 realtime data channel message: {err}"); + return; + } + }; + if let Some(event) = parse_realtime_event(payload) + && tx_event.send(event).is_err() + { + debug!("dropping realtime event because receiver closed"); + } + }))); + } + + { + let is_closed = Arc::clone(&is_closed); + let tx_open = Arc::clone(&tx_open); + data_channel.on_state_change(Some(Box::new(move |state| match state { + DataChannelState::Connecting => {} + DataChannelState::Open => { + if let Ok(mut tx_open) = tx_open.lock() + && let Some(tx_open) = tx_open.take() + { + let _ = tx_open.send(()); + } + } + DataChannelState::Closing | DataChannelState::Closed => { + is_closed.store(true, Ordering::SeqCst); + } + }))); + } + + let offer = peer_connection + .create_offer(OfferOptions { + ice_restart: false, + offer_to_receive_audio: true, + offer_to_receive_video: false, + }) + .await + .map_err(|err| ApiError::Stream(format!("failed to create WebRTC offer: {err}")))?; + + peer_connection + .set_local_description(offer.clone()) + .await + .map_err(|err| ApiError::Stream(format!("failed to set local description: {err}")))?; + + let url = realtime_calls_url(&self.provider.base_url)?; + let headers = merge_request_headers(&self.provider.headers, extra_headers, default_headers); + info!(url = %url, "posting realtime WebRTC offer"); + let http_client = Client::new(); + let mut request = http_client + .post(url) + .multipart(session_form(&config, &offer)?); + for (name, value) in &headers { + request = request.header(name, value); + } + + let response = request.send().await.map_err(|err| { + ApiError::Stream(format!("failed to post realtime WebRTC offer: {err}")) + })?; + let status = response.status(); + let answer_sdp = response.text().await.map_err(|err| { + ApiError::Stream(format!("failed to read realtime WebRTC answer body: {err}")) + })?; + if !status.is_success() { + return Err(ApiError::Stream(format!( + "realtime WebRTC offer failed with HTTP {status}: {answer_sdp}" + ))); + } + + let answer = SessionDescription::parse(&answer_sdp, SdpType::Answer) + .map_err(|err| ApiError::Stream(format!("failed to parse WebRTC answer SDP: {err}")))?; + peer_connection + .set_remote_description(answer) + .await + .map_err(|err| ApiError::Stream(format!("failed to set remote description: {err}")))?; + + if tokio::time::timeout(Duration::from_secs(10), rx_open) + .await + .is_err() + { + warn!("timed out waiting for realtime data channel to open"); + } + + Ok(RealtimeWebrtcConnection::new( + peer_connection, + data_channel, + rx_event, + is_closed, + )) + } +} + +fn realtime_calls_url(base_url: &str) -> Result { + let mut url = + Url::parse(base_url).map_err(|err| ApiError::Stream(format!("invalid base URL: {err}")))?; + url.set_path(REALTIME_CALLS_PATH); + Ok(url) +} + +fn session_form( + config: &RealtimeSessionConfig, + offer: &SessionDescription, +) -> Result { + let session_json = serde_json::to_string(&session_payload(config)) + .map_err(|err| ApiError::Stream(format!("failed to serialize realtime session: {err}")))?; + + Ok(Form::new() + .text("sdp", offer.to_string()) + .text("session", session_json)) +} + +fn session_payload(config: &RealtimeSessionConfig) -> serde_json::Value { + let mut session = json!({ + "type": "realtime", + "instructions": config.instructions, + "output_modalities": ["audio"], + "audio": { + "output": { + "voice": REALTIME_VOICE, + }, + "input": { + "turn_detection": { + "type": "server_vad", + "interrupt_response": true, + "create_response": true, + }, + }, + }, + "tools": [ + { + "type": "function", + "name": "codex", + "description": "Delegate the user's request to Codex.", + "parameters": { + "type": "object", + "properties": { + "input_transcript": { + "type": "string", + "description": "Transcript of the user's request.", + }, + "send_immediately": { + "type": "boolean", + "description": "Whether Codex should receive the request immediately.", + }, + }, + "required": ["input_transcript"], + }, + }, + { + "type": "function", + "name": "cancel_current_operation", + "description": "Cancel the current Codex operation.", + "parameters": { + "type": "object", + "properties": {}, + "required": [], + }, + }, + { + "type": "function", + "name": "turn_off_realtime_mode", + "description": "Turn off realtime voice mode.", + "parameters": { + "type": "object", + "properties": {}, + "required": [], + }, + }, + ], + "tool_choice": "auto", + }); + + if let Some(model) = &config.model { + session["model"] = json!(model); + } + session +} + +fn merge_request_headers( + provider_headers: &HeaderMap, + extra_headers: HeaderMap, + default_headers: HeaderMap, +) -> HeaderMap { + let mut headers = provider_headers.clone(); + headers.extend(extra_headers); + for (name, value) in &default_headers { + if let http::header::Entry::Vacant(entry) = headers.entry(name) { + entry.insert(value.clone()); + } + } + headers +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn realtime_calls_url_uses_webrtc_calls_path() { + assert_eq!( + realtime_calls_url("https://api.openai.com") + .expect("url") + .as_str(), + "https://api.openai.com/v1/realtime/calls" + ); + } + + #[test] + fn session_form_contains_sdp_and_session_payload() { + let offer = SessionDescription::parse( + "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n", + SdpType::Offer, + ) + .expect("offer"); + + let form = session_form( + &RealtimeSessionConfig { + instructions: "backend prompt".to_string(), + model: Some("gpt-realtime-1.5".to_string()), + session_id: Some("sess_123".to_string()), + }, + &offer, + ) + .expect("form"); + + let body = form.boundary().to_string(); + assert!(!body.is_empty()); + } + + #[test] + fn session_payload_omits_session_id_from_body() { + let payload = session_payload(&RealtimeSessionConfig { + instructions: "backend prompt".to_string(), + model: Some("gpt-realtime-1.5".to_string()), + session_id: Some("sess_123".to_string()), + }); + + assert_eq!(payload.get("id"), None); + assert_eq!(payload.get("model"), Some(&json!("gpt-realtime-1.5"))); + } + + #[test] + fn peer_connection_factory_smoke() { + let factory = PeerConnectionFactory::default(); + let _pc = factory + .create_peer_connection(RtcConfiguration::default()) + .expect("pc"); + } +} diff --git a/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs b/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs deleted file mode 100644 index 9a1fb348d4..0000000000 --- a/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs +++ /dev/null @@ -1,2067 +0,0 @@ -use crate::endpoint::realtime_websocket::protocol::ConversationItem; -use crate::endpoint::realtime_websocket::protocol::ConversationItemContent; -use crate::endpoint::realtime_websocket::protocol::RealtimeAudioFrame; -use crate::endpoint::realtime_websocket::protocol::RealtimeEvent; -use crate::endpoint::realtime_websocket::protocol::RealtimeOutboundMessage; -use crate::endpoint::realtime_websocket::protocol::RealtimeSessionConfig; -use crate::endpoint::realtime_websocket::protocol::RealtimeToolAction; -use crate::endpoint::realtime_websocket::protocol::RealtimeTranscriptDelta; -use crate::endpoint::realtime_websocket::protocol::RealtimeTranscriptEntry; -use crate::endpoint::realtime_websocket::protocol::SessionAudio; -use crate::endpoint::realtime_websocket::protocol::SessionAudioFormat; -use crate::endpoint::realtime_websocket::protocol::SessionAudioInput; -use crate::endpoint::realtime_websocket::protocol::SessionAudioOutput; -use crate::endpoint::realtime_websocket::protocol::SessionAudioOutputFormat; -use crate::endpoint::realtime_websocket::protocol::SessionNoiseReduction; -use crate::endpoint::realtime_websocket::protocol::SessionTool; -use crate::endpoint::realtime_websocket::protocol::SessionToolParameters; -use crate::endpoint::realtime_websocket::protocol::SessionToolProperty; -use crate::endpoint::realtime_websocket::protocol::SessionTurnDetection; -use crate::endpoint::realtime_websocket::protocol::SessionUpdateSession; -use crate::endpoint::realtime_websocket::protocol::parse_realtime_event; -use crate::error::ApiError; -use crate::provider::Provider; -use codex_utils_rustls_provider::ensure_rustls_crypto_provider; -use futures::SinkExt; -use futures::StreamExt; -use http::HeaderMap; -use http::HeaderValue; -use serde_json::json; -use std::collections::BTreeMap; -use std::collections::HashMap; -use std::sync::Arc; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use tokio::net::TcpStream; -use tokio::sync::Mutex; -use tokio::sync::mpsc; -use tokio::sync::oneshot; -use tokio_tungstenite::MaybeTlsStream; -use tokio_tungstenite::WebSocketStream; -use tokio_tungstenite::tungstenite::Error as WsError; -use tokio_tungstenite::tungstenite::Message; -use tokio_tungstenite::tungstenite::client::IntoClientRequest; -use tracing::debug; -use tracing::error; -use tracing::info; -use tracing::trace; -use tungstenite::protocol::WebSocketConfig; -use url::Url; - -const REALTIME_AUDIO_FORMAT: &str = "audio/pcm"; -const REALTIME_SAMPLE_RATE_HZ: u32 = 24_000; -const REALTIME_NOISE_REDUCTION: &str = "near_field"; -const REALTIME_TURN_DETECTION: &str = "server_vad"; -const REALTIME_INTERRUPT_RESPONSE: bool = true; -const REALTIME_CREATE_RESPONSE: bool = true; -const REALTIME_VOICE: &str = "marin"; - -struct WsStream { - tx_command: mpsc::Sender, - pump_task: tokio::task::JoinHandle<()>, -} - -enum WsCommand { - Send { - message: Message, - tx_result: oneshot::Sender>, - }, - Close { - tx_result: oneshot::Sender>, - }, -} - -impl WsStream { - fn new( - inner: WebSocketStream>, - ) -> (Self, mpsc::UnboundedReceiver>) { - let (tx_command, mut rx_command) = mpsc::channel::(32); - let (tx_message, rx_message) = mpsc::unbounded_channel::>(); - - let pump_task = tokio::spawn(async move { - let mut inner = inner; - loop { - tokio::select! { - command = rx_command.recv() => { - let Some(command) = command else { - break; - }; - match command { - WsCommand::Send { message, tx_result } => { - debug!("realtime websocket sending message"); - let result = inner.send(message).await; - let should_break = result.is_err(); - if let Err(err) = &result { - error!("realtime websocket send failed: {err}"); - } - let _ = tx_result.send(result); - if should_break { - break; - } - } - WsCommand::Close { tx_result } => { - info!("realtime websocket sending close"); - let result = inner.close(None).await; - if let Err(err) = &result { - error!("realtime websocket close failed: {err}"); - } - let _ = tx_result.send(result); - break; - } - } - } - message = inner.next() => { - let Some(message) = message else { - break; - }; - match message { - Ok(Message::Ping(payload)) => { - trace!(payload_len = payload.len(), "realtime websocket received ping"); - if let Err(err) = inner.send(Message::Pong(payload)).await { - error!("realtime websocket failed to send pong: {err}"); - let _ = tx_message.send(Err(err)); - break; - } - } - Ok(Message::Pong(_)) => {} - Ok(message @ (Message::Text(_) - | Message::Binary(_) - | Message::Close(_) - | Message::Frame(_))) => { - let is_close = matches!(message, Message::Close(_)); - match &message { - Message::Text(_) => trace!("realtime websocket received text frame"), - Message::Binary(binary) => { - error!( - payload_len = binary.len(), - "realtime websocket received unexpected binary frame" - ); - } - Message::Close(frame) => info!( - "realtime websocket received close frame: code={:?} reason={:?}", - frame.as_ref().map(|frame| frame.code), - frame.as_ref().map(|frame| frame.reason.as_str()) - ), - Message::Frame(_) => { - trace!("realtime websocket received raw frame"); - } - Message::Ping(_) | Message::Pong(_) => {} - } - if tx_message.send(Ok(message)).is_err() { - break; - } - if is_close { - break; - } - } - Err(err) => { - error!("realtime websocket receive failed: {err}"); - let _ = tx_message.send(Err(err)); - break; - } - } - } - } - } - info!("realtime websocket pump exiting"); - }); - - ( - Self { - tx_command, - pump_task, - }, - rx_message, - ) - } - - async fn request( - &self, - make_command: impl FnOnce(oneshot::Sender>) -> WsCommand, - ) -> Result<(), WsError> { - let (tx_result, rx_result) = oneshot::channel(); - if self.tx_command.send(make_command(tx_result)).await.is_err() { - return Err(WsError::ConnectionClosed); - } - rx_result.await.unwrap_or(Err(WsError::ConnectionClosed)) - } - - async fn send(&self, message: Message) -> Result<(), WsError> { - self.request(|tx_result| WsCommand::Send { message, tx_result }) - .await - } - - async fn close(&self) -> Result<(), WsError> { - self.request(|tx_result| WsCommand::Close { tx_result }) - .await - } -} - -impl Drop for WsStream { - fn drop(&mut self) { - self.pump_task.abort(); - } -} - -pub struct RealtimeWebsocketConnection { - writer: RealtimeWebsocketWriter, - events: RealtimeWebsocketEvents, -} - -#[derive(Clone)] -pub struct RealtimeWebsocketWriter { - stream: Arc, - is_closed: Arc, -} - -#[derive(Clone)] -pub struct RealtimeWebsocketEvents { - rx_message: Arc>>>, - active_transcript: Arc>, - is_closed: Arc, -} - -#[derive(Default)] -struct ActiveTranscriptState { - entries: Vec, -} - -impl RealtimeWebsocketConnection { - pub async fn send_audio_frame(&self, frame: RealtimeAudioFrame) -> Result<(), ApiError> { - self.writer.send_audio_frame(frame).await - } - - pub async fn send_conversation_item_create(&self, text: String) -> Result<(), ApiError> { - self.writer.send_conversation_item_create(text).await - } - - pub async fn send_conversation_handoff_append( - &self, - handoff_id: String, - output_text: String, - ) -> Result<(), ApiError> { - self.writer - .send_conversation_handoff_append(handoff_id, output_text) - .await - } - - pub async fn send_function_call_output( - &self, - call_id: String, - output_text: String, - ) -> Result<(), ApiError> { - self.writer - .send_function_call_output(call_id, output_text) - .await - } - - pub async fn send_response_create(&self) -> Result<(), ApiError> { - self.writer.send_response_create().await - } - - pub async fn send_conversation_item_truncate( - &self, - item_id: String, - content_index: u32, - audio_end_ms: u32, - ) -> Result<(), ApiError> { - self.writer - .send_conversation_item_truncate(item_id, content_index, audio_end_ms) - .await - } - - pub async fn close(&self) -> Result<(), ApiError> { - self.writer.close().await - } - - pub async fn next_event(&self) -> Result, ApiError> { - self.events.next_event().await - } - - pub fn writer(&self) -> RealtimeWebsocketWriter { - self.writer.clone() - } - - pub fn events(&self) -> RealtimeWebsocketEvents { - self.events.clone() - } - - fn new( - stream: WsStream, - rx_message: mpsc::UnboundedReceiver>, - ) -> Self { - let stream = Arc::new(stream); - let is_closed = Arc::new(AtomicBool::new(false)); - Self { - writer: RealtimeWebsocketWriter { - stream: Arc::clone(&stream), - is_closed: Arc::clone(&is_closed), - }, - events: RealtimeWebsocketEvents { - rx_message: Arc::new(Mutex::new(rx_message)), - active_transcript: Arc::new(Mutex::new(ActiveTranscriptState::default())), - is_closed, - }, - } - } -} - -impl RealtimeWebsocketWriter { - pub async fn send_audio_frame(&self, frame: RealtimeAudioFrame) -> Result<(), ApiError> { - self.send_json(RealtimeOutboundMessage::InputAudioBufferAppend { audio: frame.data }) - .await - } - - pub async fn send_conversation_item_create(&self, text: String) -> Result<(), ApiError> { - self.send_json(RealtimeOutboundMessage::ConversationItemCreate { - item: ConversationItem::Message { - role: "user".to_string(), - content: vec![ConversationItemContent { - kind: "input_text".to_string(), - text, - }], - }, - }) - .await - } - - pub async fn send_conversation_handoff_append( - &self, - _handoff_id: String, - output_text: String, - ) -> Result<(), ApiError> { - self.send_json(RealtimeOutboundMessage::ConversationItemCreate { - item: ConversationItem::Message { - role: "assistant".to_string(), - content: vec![ConversationItemContent { - kind: "output_text".to_string(), - text: output_text, - }], - }, - }) - .await - } - - pub async fn send_function_call_output( - &self, - call_id: String, - output_text: String, - ) -> Result<(), ApiError> { - let output = json!({ - "content": output_text, - }) - .to_string(); - self.send_json(RealtimeOutboundMessage::ConversationItemCreate { - item: ConversationItem::FunctionCallOutput { call_id, output }, - }) - .await - } - - pub async fn send_response_create(&self) -> Result<(), ApiError> { - self.send_json(RealtimeOutboundMessage::ResponseCreate) - .await - } - - pub async fn send_conversation_item_truncate( - &self, - item_id: String, - content_index: u32, - audio_end_ms: u32, - ) -> Result<(), ApiError> { - self.send_json(RealtimeOutboundMessage::ConversationItemTruncate { - item_id, - content_index, - audio_end_ms, - }) - .await - } - - pub async fn send_session_update(&self, instructions: String) -> Result<(), ApiError> { - self.send_json(RealtimeOutboundMessage::SessionUpdate { - session: Box::new(SessionUpdateSession { - kind: "realtime".to_string(), - instructions, - output_modalities: vec!["audio".to_string()], - audio: SessionAudio { - input: SessionAudioInput { - format: SessionAudioFormat { - kind: REALTIME_AUDIO_FORMAT.to_string(), - rate: REALTIME_SAMPLE_RATE_HZ, - }, - noise_reduction: SessionNoiseReduction { - kind: REALTIME_NOISE_REDUCTION.to_string(), - }, - turn_detection: SessionTurnDetection { - kind: REALTIME_TURN_DETECTION.to_string(), - interrupt_response: REALTIME_INTERRUPT_RESPONSE, - create_response: REALTIME_CREATE_RESPONSE, - }, - }, - output: SessionAudioOutput { - format: SessionAudioOutputFormat { - kind: REALTIME_AUDIO_FORMAT.to_string(), - rate: REALTIME_SAMPLE_RATE_HZ, - }, - voice: REALTIME_VOICE.to_string(), - }, - }, - tools: vec![ - SessionTool { - kind: "function".to_string(), - name: "codex".to_string(), - description: - "Delegate a request to Codex and return the final result to the user. Use this as the default action. If the user asks to do something next, later, after this, or once current work finishes, call this tool so the work is actually queued instead of merely promising to do it later." - .to_string(), - parameters: SessionToolParameters { - kind: "object".to_string(), - properties: BTreeMap::from([ - ( - "prompt".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "The user request to delegate to Codex." - .to_string(), - }, - ), - ( - "send_immediately".to_string(), - SessionToolProperty { - kind: "boolean".to_string(), - description: "When true, send this to Codex immediately, steering any running turn. When false or omitted, queue it for the next turn.".to_string(), - }, - ), - ]), - required: vec!["prompt".to_string()], - }, - }, - SessionTool { - kind: "function".to_string(), - name: "cancel_current_operation".to_string(), - description: "Cancel the current Codex operation, equivalent to pressing Ctrl-C without exiting Codex.".to_string(), - parameters: SessionToolParameters { - kind: "object".to_string(), - properties: BTreeMap::new(), - required: Vec::new(), - }, - }, - SessionTool { - kind: "function".to_string(), - name: "turn_off_realtime_mode".to_string(), - description: "Turn off realtime voice mode, equivalent to using /realtime to stop live voice.".to_string(), - parameters: SessionToolParameters { - kind: "object".to_string(), - properties: BTreeMap::new(), - required: Vec::new(), - }, - }, - SessionTool { - kind: "function".to_string(), - name: "manage_message_queue".to_string(), - description: "Inspect or edit queued draft messages. Prefer this over codex when the user asks what is queued or wants to replace, remove, or clear queued draft work. Supported actions: list, replace_last, remove_last, clear. Queue editing affects queued draft messages only, not pending steers.".to_string(), - parameters: SessionToolParameters { - kind: "object".to_string(), - properties: BTreeMap::from([ - ( - "action".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Queue action to run. Use one of: list, replace_last, remove_last, clear.".to_string(), - }, - ), - ( - "message".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Replacement text for action=replace_last.".to_string(), - }, - ), - ]), - required: vec!["action".to_string()], - }, - }, - SessionTool { - kind: "function".to_string(), - name: "manage_runtime_settings".to_string(), - description: "Inspect or update runtime settings for future Codex turns, and inspect quick local context like the current working_directory and git_branch. Prefer this over codex when the user wants to inspect or change model, working_directory, reasoning_effort, fast_mode, personality, or collaboration_mode, or asks quick local questions like which branch Codex is on. Call with no setting fields to list current settings, current local context, possible settings, and allowed values. Supported writable setting keys: model, working_directory, reasoning_effort, fast_mode, personality, collaboration_mode. git_branch is read-only context. Changes are not persisted to disk.".to_string(), - parameters: SessionToolParameters { - kind: "object".to_string(), - properties: BTreeMap::from([ - ( - "model".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Optional model slug to use for future Codex turns.".to_string(), - }, - ), - ( - "working_directory".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Optional working directory for future Codex turns. Relative paths are resolved against the current working directory.".to_string(), - }, - ), - ( - "reasoning_effort".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Optional reasoning effort. Supported values: default, minimal, low, medium, high, xhigh.".to_string(), - }, - ), - ( - "fast_mode".to_string(), - SessionToolProperty { - kind: "boolean".to_string(), - description: "Optional Fast mode toggle for future Codex turns.".to_string(), - }, - ), - ( - "personality".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Optional personality. Supported values: none, friendly, pragmatic.".to_string(), - }, - ), - ( - "collaboration_mode".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Optional collaboration mode. Supported values: default, plan.".to_string(), - }, - ), - ]), - required: Vec::new(), - }, - }, - SessionTool { - kind: "function".to_string(), - name: "run_tui_command".to_string(), - description: "Run a small set of built-in TUI actions. Prefer this over codex for built-in control actions like compact, review, plan, diff, or opening the agent picker. Supported commands: compact, review, plan, diff, agent. review can take an optional prompt for custom review instructions. plan can take an optional prompt to switch to Plan mode and submit work immediately.".to_string(), - parameters: SessionToolParameters { - kind: "object".to_string(), - properties: BTreeMap::from([ - ( - "command".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Built-in TUI command to run. Use one of: compact, review, plan, diff, agent.".to_string(), - }, - ), - ( - "prompt".to_string(), - SessionToolProperty { - kind: "string".to_string(), - description: "Optional text argument for command=review or command=plan.".to_string(), - }, - ), - ]), - required: vec!["command".to_string()], - }, - }, - ], - tool_choice: "auto".to_string(), - }), - }) - .await - } - - pub async fn close(&self) -> Result<(), ApiError> { - if self.is_closed.swap(true, Ordering::SeqCst) { - return Ok(()); - } - if let Err(err) = self.stream.close().await - && !matches!(err, WsError::ConnectionClosed | WsError::AlreadyClosed) - { - return Err(ApiError::Stream(format!( - "failed to close websocket: {err}" - ))); - } - Ok(()) - } - - async fn send_json(&self, message: RealtimeOutboundMessage) -> Result<(), ApiError> { - let payload = serde_json::to_string(&message) - .map_err(|err| ApiError::Stream(format!("failed to encode realtime request: {err}")))?; - debug!(?message, "realtime websocket request"); - - if self.is_closed.load(Ordering::SeqCst) { - return Err(ApiError::Stream( - "realtime websocket connection is closed".to_string(), - )); - } - - self.stream - .send(Message::Text(payload.into())) - .await - .map_err(|err| ApiError::Stream(format!("failed to send realtime request: {err}")))?; - Ok(()) - } -} - -impl RealtimeWebsocketEvents { - pub async fn next_event(&self) -> Result, ApiError> { - if self.is_closed.load(Ordering::SeqCst) { - return Ok(None); - } - - loop { - let msg = match self.rx_message.lock().await.recv().await { - Some(Ok(msg)) => msg, - Some(Err(err)) => { - self.is_closed.store(true, Ordering::SeqCst); - error!("realtime websocket read failed: {err}"); - return Err(ApiError::Stream(format!( - "failed to read websocket message: {err}" - ))); - } - None => { - self.is_closed.store(true, Ordering::SeqCst); - info!("realtime websocket event stream ended"); - return Ok(None); - } - }; - - match msg { - Message::Text(text) => { - if let Some(mut event) = parse_realtime_event(&text) { - self.update_active_transcript(&mut event).await; - log_realtime_event(&event); - debug!(?event, "realtime websocket parsed event"); - return Ok(Some(event)); - } - debug!("realtime websocket ignored unsupported text frame"); - } - Message::Close(frame) => { - self.is_closed.store(true, Ordering::SeqCst); - info!( - "realtime websocket closed: code={:?} reason={:?}", - frame.as_ref().map(|frame| frame.code), - frame.as_ref().map(|frame| frame.reason.as_str()) - ); - return Ok(None); - } - Message::Binary(_) => { - return Ok(Some(RealtimeEvent::Error( - "unexpected binary realtime websocket event".to_string(), - ))); - } - Message::Frame(_) | Message::Ping(_) | Message::Pong(_) => {} - } - } - } - - async fn update_active_transcript(&self, event: &mut RealtimeEvent) { - let mut active_transcript = self.active_transcript.lock().await; - match event { - RealtimeEvent::InputTranscriptDelta(RealtimeTranscriptDelta { delta }) => { - append_transcript_delta(&mut active_transcript.entries, "user", delta); - } - RealtimeEvent::OutputTranscriptDelta(RealtimeTranscriptDelta { delta }) => { - append_transcript_delta(&mut active_transcript.entries, "assistant", delta); - } - RealtimeEvent::HandoffRequested(handoff) => { - handoff.active_transcript = std::mem::take(&mut active_transcript.entries); - } - RealtimeEvent::SessionUpdated { .. } - | RealtimeEvent::InterruptRequested(_) - | RealtimeEvent::CloseRequested(_) - | RealtimeEvent::AudioOut(_) - | RealtimeEvent::InputAudioSpeechStarted(_) - | RealtimeEvent::ResponseCancelled(_) - | RealtimeEvent::ConversationItemAdded(_) - | RealtimeEvent::ConversationItemDone { .. } - | RealtimeEvent::ToolActionRequested(_) - | RealtimeEvent::Error(_) => {} - } - } -} - -fn append_transcript_delta(entries: &mut Vec, role: &str, delta: &str) { - if delta.is_empty() { - return; - } - - if let Some(last_entry) = entries.last_mut() - && last_entry.role == role - { - last_entry.text.push_str(delta); - return; - } - - entries.push(RealtimeTranscriptEntry { - role: role.to_string(), - text: delta.to_string(), - }); -} - -pub struct RealtimeWebsocketClient { - provider: Provider, -} - -impl RealtimeWebsocketClient { - pub fn new(provider: Provider) -> Self { - Self { provider } - } - - pub async fn connect( - &self, - config: RealtimeSessionConfig, - extra_headers: HeaderMap, - default_headers: HeaderMap, - ) -> Result { - ensure_rustls_crypto_provider(); - let ws_url = websocket_url_from_api_url( - self.provider.base_url.as_str(), - self.provider.query_params.as_ref(), - config.model.as_deref(), - )?; - - let mut request = ws_url - .as_str() - .into_client_request() - .map_err(|err| ApiError::Stream(format!("failed to build websocket request: {err}")))?; - let headers = merge_request_headers( - &self.provider.headers, - with_session_id_header(extra_headers, config.session_id.as_deref())?, - default_headers, - ); - request.headers_mut().extend(headers); - - info!("connecting realtime websocket: {ws_url}"); - let (stream, response) = - tokio_tungstenite::connect_async_with_config(request, Some(websocket_config()), false) - .await - .map_err(|err| { - ApiError::Stream(format!("failed to connect realtime websocket: {err}")) - })?; - info!( - ws_url = %ws_url, - status = %response.status(), - "realtime websocket connected" - ); - - let (stream, rx_message) = WsStream::new(stream); - let connection = RealtimeWebsocketConnection::new(stream, rx_message); - info!( - requested_session_id = config.session_id.as_deref().unwrap_or(""), - model = config.model.as_deref().unwrap_or(""), - input_audio_format = REALTIME_AUDIO_FORMAT, - input_sample_rate_hz = REALTIME_SAMPLE_RATE_HZ, - output_audio_format = REALTIME_AUDIO_FORMAT, - output_sample_rate_hz = REALTIME_SAMPLE_RATE_HZ, - voice = REALTIME_VOICE, - turn_detection = REALTIME_TURN_DETECTION, - interrupt_response = REALTIME_INTERRUPT_RESPONSE, - create_response = REALTIME_CREATE_RESPONSE, - noise_reduction = REALTIME_NOISE_REDUCTION, - "sending realtime session.update" - ); - connection - .writer - .send_session_update(config.instructions) - .await?; - Ok(connection) - } -} - -fn merge_request_headers( - provider_headers: &HeaderMap, - extra_headers: HeaderMap, - default_headers: HeaderMap, -) -> HeaderMap { - let mut headers = provider_headers.clone(); - headers.extend(extra_headers); - for (name, value) in &default_headers { - if let http::header::Entry::Vacant(entry) = headers.entry(name) { - entry.insert(value.clone()); - } - } - headers -} - -fn log_realtime_event(event: &RealtimeEvent) { - match event { - RealtimeEvent::SessionUpdated { session_id, .. } => { - info!(session_id = %session_id, "realtime session updated"); - } - RealtimeEvent::HandoffRequested(handoff) => { - info!( - function_name = "codex", - call_id = %handoff.handoff_id, - arguments = %json!({ - "prompt": handoff.input_transcript, - "send_immediately": handoff.send_immediately, - }), - "realtime function call requested" - ); - } - RealtimeEvent::InterruptRequested(interrupt) => { - info!( - function_name = "cancel_current_operation", - call_id = %interrupt.call_id, - arguments = %json!({}), - "realtime function call requested" - ); - } - RealtimeEvent::CloseRequested(close) => { - info!( - function_name = "turn_off_realtime_mode", - call_id = %close.call_id, - arguments = %json!({}), - "realtime function call requested" - ); - } - RealtimeEvent::ToolActionRequested(request) => { - let (function_name, arguments) = normalized_realtime_tool_call(&request.action); - info!( - function_name, - call_id = %request.call_id, - arguments = %arguments, - "realtime function call requested" - ); - } - _ => {} - } -} - -fn normalized_realtime_tool_call(action: &RealtimeToolAction) -> (&'static str, serde_json::Value) { - match action { - RealtimeToolAction::ManageMessageQueue { action, message } => ( - "manage_message_queue", - json!({ - "action": action, - "message": message, - }), - ), - RealtimeToolAction::ListMessageQueue => ( - "manage_message_queue", - json!({ - "action": "list", - }), - ), - RealtimeToolAction::ReplaceLastQueuedMessage { message } => ( - "manage_message_queue", - json!({ - "action": "replace_last", - "message": message, - }), - ), - RealtimeToolAction::RemoveLastQueuedMessage => ( - "manage_message_queue", - json!({ - "action": "remove_last", - }), - ), - RealtimeToolAction::ClearQueuedMessages => ( - "manage_message_queue", - json!({ - "action": "clear", - }), - ), - RealtimeToolAction::ManageRuntimeSettings { - model, - working_directory, - reasoning_effort, - fast_mode, - personality, - collaboration_mode, - } - | RealtimeToolAction::UpdateRuntimeSettings { - model, - working_directory, - reasoning_effort, - fast_mode, - personality, - collaboration_mode, - } => ( - "manage_runtime_settings", - json!({ - "model": model, - "working_directory": working_directory, - "reasoning_effort": reasoning_effort, - "fast_mode": fast_mode, - "personality": personality, - "collaboration_mode": collaboration_mode, - }), - ), - RealtimeToolAction::ListRuntimeSettings => ("manage_runtime_settings", json!({})), - RealtimeToolAction::RunTuiCommand { command, prompt } => ( - "run_tui_command", - json!({ - "command": command, - "prompt": prompt, - }), - ), - RealtimeToolAction::CompactConversation => ( - "run_tui_command", - json!({ - "command": "compact", - }), - ), - } -} - -fn with_session_id_header( - mut headers: HeaderMap, - session_id: Option<&str>, -) -> Result { - let Some(session_id) = session_id else { - return Ok(headers); - }; - headers.insert( - "x-session-id", - HeaderValue::from_str(session_id).map_err(|err| { - ApiError::Stream(format!("invalid realtime session id header: {err}")) - })?, - ); - Ok(headers) -} - -fn websocket_config() -> WebSocketConfig { - WebSocketConfig::default() -} - -fn websocket_url_from_api_url( - api_url: &str, - query_params: Option<&HashMap>, - model: Option<&str>, -) -> Result { - let mut url = Url::parse(api_url) - .map_err(|err| ApiError::Stream(format!("failed to parse realtime api_url: {err}")))?; - - normalize_realtime_path(&mut url); - - match url.scheme() { - "ws" | "wss" => {} - "http" | "https" => { - let scheme = if url.scheme() == "http" { "ws" } else { "wss" }; - let _ = url.set_scheme(scheme); - } - scheme => { - return Err(ApiError::Stream(format!( - "unsupported realtime api_url scheme: {scheme}" - ))); - } - } - - let has_additional_query_params = query_params - .is_some_and(|params| params.keys().any(|key| key != "model" || model.is_none())); - if model.is_some() || has_additional_query_params { - let mut query = url.query_pairs_mut(); - if let Some(model) = model { - query.append_pair("model", model); - } - if let Some(query_params) = query_params { - for (key, value) in query_params { - if key == "model" && model.is_some() { - continue; - } - query.append_pair(key, value); - } - } - } - - Ok(url) -} - -fn normalize_realtime_path(url: &mut Url) { - let path = url.path().to_string(); - if path.is_empty() || path == "/" { - url.set_path("/v1/realtime"); - return; - } - - if path.ends_with("/realtime") { - return; - } - - if path.ends_with("/realtime/") { - url.set_path(path.trim_end_matches('/')); - return; - } - - if path.ends_with("/v1") { - url.set_path(&format!("{path}/realtime")); - return; - } - - if path.ends_with("/v1/") { - url.set_path(&format!("{path}realtime")); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::endpoint::realtime_websocket::protocol::RealtimeCloseRequested; - use crate::endpoint::realtime_websocket::protocol::RealtimeHandoffRequested; - use crate::endpoint::realtime_websocket::protocol::RealtimeInputAudioSpeechStarted; - use crate::endpoint::realtime_websocket::protocol::RealtimeInterruptRequested; - use crate::endpoint::realtime_websocket::protocol::RealtimeOutputAudioDelta; - use crate::endpoint::realtime_websocket::protocol::RealtimeResponseCancelled; - use crate::endpoint::realtime_websocket::protocol::RealtimeToolAction; - use crate::endpoint::realtime_websocket::protocol::RealtimeToolActionRequested; - use crate::endpoint::realtime_websocket::protocol::RealtimeTranscriptDelta; - use crate::endpoint::realtime_websocket::protocol::RealtimeTranscriptEntry; - use http::HeaderValue; - use pretty_assertions::assert_eq; - use serde_json::Value; - use serde_json::json; - use std::collections::HashMap; - use std::time::Duration; - use tokio::net::TcpListener; - use tokio_tungstenite::accept_async; - use tokio_tungstenite::tungstenite::Message; - - #[test] - fn parse_session_updated_event() { - let payload = json!({ - "type": "session.updated", - "session": {"id": "sess_123", "instructions": "backend prompt"} - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::SessionUpdated { - session_id: "sess_123".to_string(), - instructions: Some("backend prompt".to_string()), - }) - ); - } - - #[test] - fn parse_audio_delta_event() { - let payload = json!({ - "type": "response.output_audio.delta", - "delta": "AAA=", - "sample_rate": 48000, - "channels": 1, - "samples_per_channel": 960 - }) - .to_string(); - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::AudioOut(RealtimeOutputAudioDelta { - frame: RealtimeAudioFrame { - data: "AAA=".to_string(), - sample_rate: 48000, - num_channels: 1, - samples_per_channel: Some(960), - }, - item_id: None, - })) - ); - } - - #[test] - fn parse_audio_delta_event_defaults_audio_shape() { - let payload = json!({ - "type": "response.output_audio.delta", - "delta": "AAA=" - }) - .to_string(); - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::AudioOut(RealtimeOutputAudioDelta { - frame: RealtimeAudioFrame { - data: "AAA=".to_string(), - sample_rate: 24_000, - num_channels: 1, - samples_per_channel: None, - }, - item_id: None, - })) - ); - } - - #[test] - fn parse_audio_delta_event_with_item_id() { - let payload = json!({ - "type": "response.audio.delta", - "delta": "AAA=", - "item_id": "item_audio_1" - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::AudioOut(RealtimeOutputAudioDelta { - frame: RealtimeAudioFrame { - data: "AAA=".to_string(), - sample_rate: 24_000, - num_channels: 1, - samples_per_channel: None, - }, - item_id: Some("item_audio_1".to_string()), - })) - ); - } - - #[test] - fn parse_conversation_item_added_event() { - let payload = json!({ - "type": "conversation.item.added", - "item": {"type": "message", "seq": 7} - }) - .to_string(); - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::ConversationItemAdded( - json!({"type": "message", "seq": 7}) - )) - ); - } - - #[test] - fn parse_conversation_item_done_event() { - let payload = json!({ - "type": "conversation.item.done", - "item": {"id": "item_123", "type": "message"} - }) - .to_string(); - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::ConversationItemDone { - item_id: "item_123".to_string(), - }) - ); - } - - #[test] - fn parse_handoff_requested_event() { - let payload = json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_123", - "type": "function_call", - "name": "codex", - "call_id": "handoff_123", - "arguments": "{\"prompt\":\"delegate this\"}" - } - ] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::HandoffRequested(RealtimeHandoffRequested { - handoff_id: "handoff_123".to_string(), - item_id: "item_123".to_string(), - input_transcript: "delegate this".to_string(), - send_immediately: false, - active_transcript: Vec::new(), - })) - ); - } - - #[test] - fn parse_handoff_requested_event_with_send_immediately() { - let payload = json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_456", - "type": "function_call", - "name": "codex", - "call_id": "handoff_456", - "arguments": "{\"prompt\":\"delegate this now\",\"send_immediately\":true}" - } - ] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::HandoffRequested(RealtimeHandoffRequested { - handoff_id: "handoff_456".to_string(), - item_id: "item_456".to_string(), - input_transcript: "delegate this now".to_string(), - send_immediately: true, - active_transcript: Vec::new(), - })) - ); - } - - #[test] - fn parse_interrupt_requested_event() { - let payload = json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_cancel", - "type": "function_call", - "name": "cancel_current_operation", - "call_id": "cancel_123", - "arguments": "{}" - } - ] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::InterruptRequested( - RealtimeInterruptRequested { - call_id: "cancel_123".to_string(), - } - )) - ); - } - - #[test] - fn parse_close_requested_event() { - let payload = json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_close", - "type": "function_call", - "name": "turn_off_realtime_mode", - "call_id": "close_123", - "arguments": "{}" - } - ] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::CloseRequested(RealtimeCloseRequested { - call_id: "close_123".to_string(), - })) - ); - } - - #[test] - fn parse_manage_message_queue_requested_event() { - let payload = json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_queue", - "type": "function_call", - "name": "manage_message_queue", - "call_id": "queue_123", - "arguments": "{\"action\":\"list\"}" - } - ] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::ToolActionRequested( - RealtimeToolActionRequested { - call_id: "queue_123".to_string(), - action: RealtimeToolAction::ManageMessageQueue { - action: "list".to_string(), - message: None, - }, - } - )) - ); - } - - #[test] - fn parse_manage_runtime_settings_requested_event() { - let payload = json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_settings", - "type": "function_call", - "name": "manage_runtime_settings", - "call_id": "settings_123", - "arguments": "{\"model\":\"gpt-5\",\"working_directory\":\"src\",\"reasoning_effort\":\"low\"}" - } - ] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::ToolActionRequested( - RealtimeToolActionRequested { - call_id: "settings_123".to_string(), - action: RealtimeToolAction::ManageRuntimeSettings { - model: Some("gpt-5".to_string()), - working_directory: Some("src".to_string()), - reasoning_effort: Some("low".to_string()), - fast_mode: None, - personality: None, - collaboration_mode: None, - }, - } - )) - ); - } - - #[test] - fn parse_run_tui_command_requested_event() { - let payload = json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_command", - "type": "function_call", - "name": "run_tui_command", - "call_id": "command_123", - "arguments": "{\"command\":\"plan\",\"prompt\":\"make a plan\"}" - } - ] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::ToolActionRequested( - RealtimeToolActionRequested { - call_id: "command_123".to_string(), - action: RealtimeToolAction::RunTuiCommand { - command: "plan".to_string(), - prompt: Some("make a plan".to_string()), - }, - } - )) - ); - } - - #[test] - fn parse_input_audio_speech_started_event() { - let payload = json!({ - "type": "input_audio_buffer.speech_started", - "item_id": "item_user_1" - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::InputAudioSpeechStarted( - RealtimeInputAudioSpeechStarted { - item_id: Some("item_user_1".to_string()), - } - )) - ); - } - - #[test] - fn parse_cancelled_response_done_event() { - let payload = json!({ - "type": "response.done", - "response": { - "id": "resp_cancelled", - "status": "cancelled", - "output": [] - } - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::ResponseCancelled( - RealtimeResponseCancelled { - response_id: Some("resp_cancelled".to_string()), - } - )) - ); - } - - #[test] - fn parse_unknown_event_as_conversation_item_added() { - let payload = json!({ - "type": "response.output_text.delta", - "delta": "hello", - "response_id": "resp_1" - }) - .to_string(); - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::ConversationItemAdded(json!({ - "type": "response.output_text.delta", - "delta": "hello", - "response_id": "resp_1" - }))) - ); - } - - #[test] - fn parse_input_transcript_delta_event() { - let payload = json!({ - "type": "conversation.input_transcript.delta", - "delta": "hello " - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::InputTranscriptDelta( - RealtimeTranscriptDelta { - delta: "hello ".to_string(), - } - )) - ); - } - - #[test] - fn parse_output_transcript_delta_event() { - let payload = json!({ - "type": "conversation.output_transcript.delta", - "delta": "hi" - }) - .to_string(); - - assert_eq!( - parse_realtime_event(payload.as_str()), - Some(RealtimeEvent::OutputTranscriptDelta( - RealtimeTranscriptDelta { - delta: "hi".to_string(), - } - )) - ); - } - - #[test] - fn merge_request_headers_matches_http_precedence() { - let mut provider_headers = HeaderMap::new(); - provider_headers.insert( - "originator", - HeaderValue::from_static("provider-originator"), - ); - provider_headers.insert("x-priority", HeaderValue::from_static("provider")); - - let mut extra_headers = HeaderMap::new(); - extra_headers.insert("x-priority", HeaderValue::from_static("extra")); - - let mut default_headers = HeaderMap::new(); - default_headers.insert("originator", HeaderValue::from_static("default-originator")); - default_headers.insert("x-priority", HeaderValue::from_static("default")); - default_headers.insert("x-default-only", HeaderValue::from_static("default-only")); - - let merged = merge_request_headers(&provider_headers, extra_headers, default_headers); - - assert_eq!( - merged.get("originator"), - Some(&HeaderValue::from_static("provider-originator")) - ); - assert_eq!( - merged.get("x-priority"), - Some(&HeaderValue::from_static("extra")) - ); - assert_eq!( - merged.get("x-default-only"), - Some(&HeaderValue::from_static("default-only")) - ); - } - - #[test] - fn websocket_url_from_http_base_defaults_to_ws_path() { - let url = - websocket_url_from_api_url("http://127.0.0.1:8011", None, None).expect("build ws url"); - assert_eq!(url.as_str(), "ws://127.0.0.1:8011/v1/realtime"); - } - - #[test] - fn websocket_url_from_ws_base_defaults_to_ws_path() { - let url = - websocket_url_from_api_url("wss://example.com", None, Some("realtime-test-model")) - .expect("build ws url"); - assert_eq!( - url.as_str(), - "wss://example.com/v1/realtime?model=realtime-test-model" - ); - } - - #[test] - fn websocket_url_from_v1_base_appends_realtime_path() { - let url = websocket_url_from_api_url("https://api.openai.com/v1", None, Some("snapshot")) - .expect("build ws url"); - assert_eq!( - url.as_str(), - "wss://api.openai.com/v1/realtime?model=snapshot" - ); - } - - #[test] - fn websocket_url_from_nested_v1_base_appends_realtime_path() { - let url = - websocket_url_from_api_url("https://example.com/openai/v1", None, Some("snapshot")) - .expect("build ws url"); - assert_eq!( - url.as_str(), - "wss://example.com/openai/v1/realtime?model=snapshot" - ); - } - - #[test] - fn websocket_url_preserves_existing_realtime_path_and_extra_query_params() { - let url = websocket_url_from_api_url( - "https://example.com/v1/realtime?foo=bar", - Some(&HashMap::from([("trace".to_string(), "1".to_string())])), - Some("snapshot"), - ) - .expect("build ws url"); - assert_eq!( - url.as_str(), - "wss://example.com/v1/realtime?foo=bar&model=snapshot&trace=1" - ); - } - - #[tokio::test] - async fn e2e_connect_and_exchange_events_against_mock_ws_server() { - let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind"); - let addr = listener.local_addr().expect("local addr"); - - let server = tokio::spawn(async move { - let (stream, _) = listener.accept().await.expect("accept"); - let mut ws = accept_async(stream).await.expect("accept ws"); - - let first = ws - .next() - .await - .expect("first msg") - .expect("first msg ok") - .into_text() - .expect("text"); - let first_json: Value = serde_json::from_str(&first).expect("json"); - assert_eq!(first_json["type"], "session.update"); - assert_eq!( - first_json["session"]["type"], - Value::String("realtime".to_string()) - ); - assert_eq!( - first_json["session"]["instructions"], - Value::String("backend prompt".to_string()) - ); - assert_eq!( - first_json["session"]["output_modalities"][0], - Value::String("audio".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["format"]["type"], - Value::String("audio/pcm".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["format"]["rate"], - Value::from(24_000) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["noise_reduction"]["type"], - Value::String("near_field".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["turn_detection"]["type"], - Value::String("server_vad".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["turn_detection"]["interrupt_response"], - Value::Bool(true) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["turn_detection"]["create_response"], - Value::Bool(true) - ); - assert_eq!( - first_json["session"]["audio"]["output"]["format"]["type"], - Value::String("audio/pcm".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["output"]["format"]["rate"], - Value::from(24_000) - ); - assert_eq!( - first_json["session"]["audio"]["output"]["voice"], - Value::String("marin".to_string()) - ); - assert_eq!( - first_json["session"]["tool_choice"], - Value::String("auto".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][0]["type"], - Value::String("function".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][0]["name"], - Value::String("codex".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][0]["parameters"]["required"][0], - Value::String("prompt".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][0]["parameters"]["properties"]["send_immediately"]["type"], - Value::String("boolean".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["type"], - Value::String("function".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["name"], - Value::String("cancel_current_operation".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["parameters"]["type"], - Value::String("object".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["parameters"]["required"], - Value::Array(Vec::new()) - ); - assert_eq!( - first_json["session"]["tools"][1]["parameters"]["properties"], - json!({}) - ); - assert_eq!( - first_json["session"]["tools"][2]["type"], - Value::String("function".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][2]["name"], - Value::String("turn_off_realtime_mode".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][2]["parameters"]["type"], - Value::String("object".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][2]["parameters"]["required"], - Value::Array(Vec::new()) - ); - assert_eq!( - first_json["session"]["tools"][2]["parameters"]["properties"], - json!({}) - ); - assert_eq!( - first_json["session"]["tools"] - .as_array() - .expect("tools array") - .iter() - .map(|tool| tool["name"].as_str().expect("tool name")) - .collect::>(), - vec![ - "codex", - "cancel_current_operation", - "turn_off_realtime_mode", - "manage_message_queue", - "manage_runtime_settings", - "run_tui_command", - ] - ); - assert_eq!( - first_json["session"]["tools"][4]["parameters"]["properties"]["working_directory"] - ["type"], - Value::String("string".to_string()) - ); - - ws.send(Message::Text( - json!({ - "type": "session.updated", - "session": {"id": "sess_mock", "instructions": "backend prompt"} - }) - .to_string() - .into(), - )) - .await - .expect("send session.updated"); - - let second = ws - .next() - .await - .expect("second msg") - .expect("second msg ok") - .into_text() - .expect("text"); - let second_json: Value = serde_json::from_str(&second).expect("json"); - assert_eq!(second_json["type"], "input_audio_buffer.append"); - - let third = ws - .next() - .await - .expect("third msg") - .expect("third msg ok") - .into_text() - .expect("text"); - let third_json: Value = serde_json::from_str(&third).expect("json"); - assert_eq!(third_json["type"], "conversation.item.create"); - assert_eq!(third_json["item"]["content"][0]["text"], "hello agent"); - - let fourth = ws - .next() - .await - .expect("fourth msg") - .expect("fourth msg ok") - .into_text() - .expect("text"); - let fourth_json: Value = serde_json::from_str(&fourth).expect("json"); - assert_eq!(fourth_json["type"], "conversation.item.create"); - assert_eq!(fourth_json["item"]["type"], "message"); - assert_eq!(fourth_json["item"]["role"], "assistant"); - assert_eq!( - fourth_json["item"]["content"][0]["type"], - Value::String("output_text".to_string()) - ); - assert_eq!( - fourth_json["item"]["content"][0]["text"], - Value::String("hello from codex".to_string()) - ); - - let fifth = ws - .next() - .await - .expect("fifth msg") - .expect("fifth msg ok") - .into_text() - .expect("text"); - let fifth_json: Value = serde_json::from_str(&fifth).expect("json"); - assert_eq!(fifth_json["type"], "conversation.item.create"); - assert_eq!(fifth_json["item"]["type"], "function_call_output"); - assert_eq!(fifth_json["item"]["call_id"], "handoff_1"); - - let sixth = ws - .next() - .await - .expect("sixth msg") - .expect("sixth msg ok") - .into_text() - .expect("text"); - let sixth_json: Value = serde_json::from_str(&sixth).expect("json"); - assert_eq!(sixth_json["type"], "response.create"); - - ws.send(Message::Text( - json!({ - "type": "response.output_audio.delta", - "delta": "AQID", - "sample_rate": 48000, - "channels": 1 - }) - .to_string() - .into(), - )) - .await - .expect("send audio"); - - ws.send(Message::Text( - json!({ - "type": "conversation.input_transcript.delta", - "delta": "delegate " - }) - .to_string() - .into(), - )) - .await - .expect("send input transcript delta"); - - ws.send(Message::Text( - json!({ - "type": "conversation.input_transcript.delta", - "delta": "now" - }) - .to_string() - .into(), - )) - .await - .expect("send input transcript delta"); - - ws.send(Message::Text( - json!({ - "type": "conversation.output_transcript.delta", - "delta": "working" - }) - .to_string() - .into(), - )) - .await - .expect("send output transcript delta"); - - ws.send(Message::Text( - json!({ - "type": "response.done", - "response": { - "output": [ - { - "id": "item_2", - "type": "function_call", - "name": "codex", - "call_id": "handoff_1", - "arguments": "{\"prompt\":\"delegate now\"}" - } - ] - } - }) - .to_string() - .into(), - )) - .await - .expect("send item added"); - }); - - let provider = Provider { - name: "test".to_string(), - base_url: format!("http://{addr}"), - query_params: Some(HashMap::new()), - headers: HeaderMap::new(), - retry: crate::provider::RetryConfig { - max_attempts: 1, - base_delay: Duration::from_millis(1), - retry_429: false, - retry_5xx: false, - retry_transport: false, - }, - stream_idle_timeout: Duration::from_secs(5), - }; - let client = RealtimeWebsocketClient::new(provider); - let connection = client - .connect( - RealtimeSessionConfig { - instructions: "backend prompt".to_string(), - model: Some("realtime-test-model".to_string()), - session_id: Some("conv_1".to_string()), - }, - HeaderMap::new(), - HeaderMap::new(), - ) - .await - .expect("connect"); - - let created = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - created, - RealtimeEvent::SessionUpdated { - session_id: "sess_mock".to_string(), - instructions: Some("backend prompt".to_string()), - } - ); - - connection - .send_audio_frame(RealtimeAudioFrame { - data: "AQID".to_string(), - sample_rate: 48000, - num_channels: 1, - samples_per_channel: Some(960), - }) - .await - .expect("send audio"); - connection - .send_conversation_item_create("hello agent".to_string()) - .await - .expect("send item"); - connection - .send_conversation_handoff_append( - "handoff_1".to_string(), - "hello from codex".to_string(), - ) - .await - .expect("send handoff"); - connection - .send_function_call_output("handoff_1".to_string(), "final from codex".to_string()) - .await - .expect("send function output"); - connection - .send_response_create() - .await - .expect("send response.create"); - - let audio_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - audio_event, - RealtimeEvent::AudioOut(RealtimeOutputAudioDelta { - frame: RealtimeAudioFrame { - data: "AQID".to_string(), - sample_rate: 48000, - num_channels: 1, - samples_per_channel: None, - }, - item_id: None, - }) - ); - - let input_delta_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - input_delta_event, - RealtimeEvent::InputTranscriptDelta(RealtimeTranscriptDelta { - delta: "delegate ".to_string(), - }) - ); - - let input_delta_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - input_delta_event, - RealtimeEvent::InputTranscriptDelta(RealtimeTranscriptDelta { - delta: "now".to_string(), - }) - ); - - let output_delta_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - output_delta_event, - RealtimeEvent::OutputTranscriptDelta(RealtimeTranscriptDelta { - delta: "working".to_string(), - }) - ); - - let added_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - added_event, - RealtimeEvent::HandoffRequested(RealtimeHandoffRequested { - handoff_id: "handoff_1".to_string(), - item_id: "item_2".to_string(), - input_transcript: "delegate now".to_string(), - send_immediately: false, - active_transcript: vec![ - RealtimeTranscriptEntry { - role: "user".to_string(), - text: "delegate now".to_string(), - }, - RealtimeTranscriptEntry { - role: "assistant".to_string(), - text: "working".to_string(), - }, - ], - }) - ); - - connection.close().await.expect("close"); - server.await.expect("server task"); - } - - #[tokio::test] - async fn send_does_not_block_while_next_event_waits_for_inbound_data() { - let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind"); - let addr = listener.local_addr().expect("local addr"); - - let server = tokio::spawn(async move { - let (stream, _) = listener.accept().await.expect("accept"); - let mut ws = accept_async(stream).await.expect("accept ws"); - - let first = ws - .next() - .await - .expect("first msg") - .expect("first msg ok") - .into_text() - .expect("text"); - let first_json: Value = serde_json::from_str(&first).expect("json"); - assert_eq!(first_json["type"], "session.update"); - - let second = ws - .next() - .await - .expect("second msg") - .expect("second msg ok") - .into_text() - .expect("text"); - let second_json: Value = serde_json::from_str(&second).expect("json"); - assert_eq!(second_json["type"], "input_audio_buffer.append"); - - ws.send(Message::Text( - json!({ - "type": "session.updated", - "session": {"id": "sess_after_send", "instructions": "backend prompt"} - }) - .to_string() - .into(), - )) - .await - .expect("send session.updated"); - }); - - let provider = Provider { - name: "test".to_string(), - base_url: format!("http://{addr}"), - query_params: Some(HashMap::new()), - headers: HeaderMap::new(), - retry: crate::provider::RetryConfig { - max_attempts: 1, - base_delay: Duration::from_millis(1), - retry_429: false, - retry_5xx: false, - retry_transport: false, - }, - stream_idle_timeout: Duration::from_secs(5), - }; - let client = RealtimeWebsocketClient::new(provider); - let connection = client - .connect( - RealtimeSessionConfig { - instructions: "backend prompt".to_string(), - model: Some("realtime-test-model".to_string()), - session_id: Some("conv_1".to_string()), - }, - HeaderMap::new(), - HeaderMap::new(), - ) - .await - .expect("connect"); - - let (send_result, next_result) = tokio::join!( - async { - tokio::time::timeout( - Duration::from_millis(200), - connection.send_audio_frame(RealtimeAudioFrame { - data: "AQID".to_string(), - sample_rate: 48000, - num_channels: 1, - samples_per_channel: Some(960), - }), - ) - .await - }, - connection.next_event() - ); - - send_result - .expect("send should not block on next_event") - .expect("send audio"); - let next_event = next_result.expect("next event").expect("event"); - assert_eq!( - next_event, - RealtimeEvent::SessionUpdated { - session_id: "sess_after_send".to_string(), - instructions: Some("backend prompt".to_string()), - } - ); - - connection.close().await.expect("close"); - server.await.expect("server task"); - } -} diff --git a/codex-rs/codex-api/src/endpoint/realtime_websocket/mod.rs b/codex-rs/codex-api/src/endpoint/realtime_websocket/mod.rs index a89dbd3e77..17d942fbd7 100644 --- a/codex-rs/codex-api/src/endpoint/realtime_websocket/mod.rs +++ b/codex-rs/codex-api/src/endpoint/realtime_websocket/mod.rs @@ -1,10 +1,3 @@ -pub mod methods; -pub mod protocol; +mod protocol; -pub use codex_protocol::protocol::RealtimeAudioFrame; -pub use codex_protocol::protocol::RealtimeEvent; -pub use methods::RealtimeWebsocketClient; -pub use methods::RealtimeWebsocketConnection; -pub use methods::RealtimeWebsocketEvents; -pub use methods::RealtimeWebsocketWriter; -pub use protocol::RealtimeSessionConfig; +pub(crate) use protocol::parse_realtime_event; diff --git a/codex-rs/codex-api/src/endpoint/realtime_websocket/protocol.rs b/codex-rs/codex-api/src/endpoint/realtime_websocket/protocol.rs index a48f23df43..365194c5f0 100644 --- a/codex-rs/codex-api/src/endpoint/realtime_websocket/protocol.rs +++ b/codex-rs/codex-api/src/endpoint/realtime_websocket/protocol.rs @@ -1,150 +1,18 @@ -pub use codex_protocol::protocol::RealtimeAudioFrame; -pub use codex_protocol::protocol::RealtimeCloseRequested; -pub use codex_protocol::protocol::RealtimeEvent; -pub use codex_protocol::protocol::RealtimeHandoffRequested; -pub use codex_protocol::protocol::RealtimeInputAudioSpeechStarted; -pub use codex_protocol::protocol::RealtimeInterruptRequested; -pub use codex_protocol::protocol::RealtimeOutputAudioDelta; -pub use codex_protocol::protocol::RealtimeResponseCancelled; -pub use codex_protocol::protocol::RealtimeToolAction; -pub use codex_protocol::protocol::RealtimeToolActionRequested; -pub use codex_protocol::protocol::RealtimeTranscriptDelta; -pub use codex_protocol::protocol::RealtimeTranscriptEntry; +use codex_protocol::protocol::RealtimeCloseRequested; +use codex_protocol::protocol::RealtimeEvent; +use codex_protocol::protocol::RealtimeHandoffRequested; +use codex_protocol::protocol::RealtimeInputAudioSpeechStarted; +use codex_protocol::protocol::RealtimeInterruptRequested; +use codex_protocol::protocol::RealtimeResponseCancelled; +use codex_protocol::protocol::RealtimeToolAction; +use codex_protocol::protocol::RealtimeToolActionRequested; +use codex_protocol::protocol::RealtimeTranscriptDelta; use serde::Deserialize; -use serde::Serialize; use serde_json::Value; -use std::collections::BTreeMap; use std::string::ToString; use tracing::debug; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RealtimeSessionConfig { - pub instructions: String, - pub model: Option, - pub session_id: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "type")] -pub(super) enum RealtimeOutboundMessage { - #[serde(rename = "input_audio_buffer.append")] - InputAudioBufferAppend { audio: String }, - #[serde(rename = "response.create")] - ResponseCreate, - #[serde(rename = "conversation.item.truncate")] - ConversationItemTruncate { - item_id: String, - content_index: u32, - audio_end_ms: u32, - }, - #[serde(rename = "session.update")] - SessionUpdate { session: Box }, - #[serde(rename = "conversation.item.create")] - ConversationItemCreate { item: ConversationItem }, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionUpdateSession { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) instructions: String, - pub(super) output_modalities: Vec, - pub(super) audio: SessionAudio, - pub(super) tools: Vec, - pub(super) tool_choice: String, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionAudio { - pub(super) input: SessionAudioInput, - pub(super) output: SessionAudioOutput, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionAudioInput { - pub(super) format: SessionAudioFormat, - pub(super) noise_reduction: SessionNoiseReduction, - pub(super) turn_detection: SessionTurnDetection, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionAudioFormat { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) rate: u32, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionNoiseReduction { - #[serde(rename = "type")] - pub(super) kind: String, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionTurnDetection { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) interrupt_response: bool, - pub(super) create_response: bool, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionAudioOutput { - pub(super) format: SessionAudioOutputFormat, - pub(super) voice: String, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionAudioOutputFormat { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) rate: u32, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionTool { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) name: String, - pub(super) description: String, - pub(super) parameters: SessionToolParameters, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionToolParameters { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) properties: BTreeMap, - pub(super) required: Vec, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct SessionToolProperty { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) description: String, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "type")] -pub(super) enum ConversationItem { - #[serde(rename = "message")] - Message { - role: String, - content: Vec, - }, - #[serde(rename = "function_call_output")] - FunctionCallOutput { call_id: String, output: String }, -} - -#[derive(Debug, Clone, Serialize)] -pub(super) struct ConversationItemContent { - #[serde(rename = "type")] - pub(super) kind: String, - pub(super) text: String, -} - -pub(super) fn parse_realtime_event(payload: &str) -> Option { +pub(crate) fn parse_realtime_event(payload: &str) -> Option { let parsed: Value = match serde_json::from_str(payload) { Ok(msg) => msg, Err(err) => { @@ -160,6 +28,7 @@ pub(super) fn parse_realtime_event(payload: &str) -> Option { return None; } }; + match message_type { "session.created" | "session.updated" => { let session_id = parsed @@ -179,42 +48,6 @@ pub(super) fn parse_realtime_event(payload: &str) -> Option { instructions, }) } - - "conversation.output_audio.delta" - | "response.output_audio.delta" - | "response.audio.delta" => { - let data = parsed - .get("delta") - .and_then(Value::as_str) - .or_else(|| parsed.get("data").and_then(Value::as_str)) - .map(str::to_string)?; - let sample_rate = parsed - .get("sample_rate") - .and_then(Value::as_u64) - .and_then(|v| u32::try_from(v).ok()) - .unwrap_or(24_000); - let num_channels = parsed - .get("channels") - .or_else(|| parsed.get("num_channels")) - .and_then(Value::as_u64) - .and_then(|v| u16::try_from(v).ok()) - .unwrap_or(1); - Some(RealtimeEvent::AudioOut(RealtimeOutputAudioDelta { - frame: RealtimeAudioFrame { - data, - sample_rate, - num_channels, - samples_per_channel: parsed - .get("samples_per_channel") - .and_then(Value::as_u64) - .and_then(|v| u32::try_from(v).ok()), - }, - item_id: parsed - .get("item_id") - .and_then(Value::as_str) - .map(str::to_string), - })) - } "input_audio_buffer.speech_started" => Some(RealtimeEvent::InputAudioSpeechStarted( RealtimeInputAudioSpeechStarted { item_id: parsed @@ -525,13 +358,6 @@ fn parse_tool_action_requested(parsed: &Value) -> Option Option } fn find_function_call<'a>(parsed: &'a Value, name: &str) -> Option<&'a Value> { - parsed + let output = parsed .get("response") .and_then(Value::as_object) .and_then(|response| response.get("output")) - .and_then(Value::as_array)? - .iter() - .find(|item| { - item.get("type").and_then(Value::as_str) == Some("function_call") - && item.get("name").and_then(Value::as_str) == Some(name) - }) + .and_then(Value::as_array)?; + output.iter().find(|item| { + item.get("type").and_then(Value::as_str) == Some("function_call") + && item.get("name").and_then(Value::as_str) == Some(name) + }) } +#[derive(Debug, Default)] struct ParsedHandoffArguments { input_transcript: String, send_immediately: bool, } fn parse_handoff_arguments(arguments: &str) -> ParsedHandoffArguments { - #[derive(Debug, Deserialize)] - struct HandoffArguments { + #[derive(Debug, Deserialize, Default)] + struct RawHandoffArguments { #[serde(default)] - prompt: Option, - #[serde(default)] - text: Option, - #[serde(default)] - input: Option, - #[serde(default)] - message: Option, - #[serde(default)] - input_transcript: Option, + input_transcript: String, #[serde(default)] send_immediately: bool, - #[serde(default)] - messages: Vec, } - let Some(parsed) = serde_json::from_str::(arguments).ok() else { - return ParsedHandoffArguments { + serde_json::from_str::(arguments) + .map(|raw| ParsedHandoffArguments { + input_transcript: raw.input_transcript, + send_immediately: raw.send_immediately, + }) + .unwrap_or_else(|_| ParsedHandoffArguments { input_transcript: arguments.to_string(), send_immediately: false, - }; - }; - - for value in [ - parsed.prompt, - parsed.text, - parsed.input, - parsed.message, - parsed.input_transcript, - ] - .into_iter() - .flatten() - { - if !value.is_empty() { - return ParsedHandoffArguments { - input_transcript: value, - send_immediately: parsed.send_immediately, - }; - } - } - - if let Some(message) = parsed - .messages - .into_iter() - .find(|message| message.role == "user" && !message.text.is_empty()) - { - return ParsedHandoffArguments { - input_transcript: message.text, - send_immediately: parsed.send_immediately, - }; - } - - ParsedHandoffArguments { - input_transcript: String::new(), - send_immediately: parsed.send_immediately, - } + }) } diff --git a/codex-rs/codex-api/src/lib.rs b/codex-rs/codex-api/src/lib.rs index 138929602b..2ba493732b 100644 --- a/codex-rs/codex-api/src/lib.rs +++ b/codex-rs/codex-api/src/lib.rs @@ -27,9 +27,9 @@ pub use crate::common::create_text_param_for_request; pub use crate::endpoint::compact::CompactClient; pub use crate::endpoint::memories::MemoriesClient; pub use crate::endpoint::models::ModelsClient; -pub use crate::endpoint::realtime_websocket::RealtimeSessionConfig; -pub use crate::endpoint::realtime_websocket::RealtimeWebsocketClient; -pub use crate::endpoint::realtime_websocket::RealtimeWebsocketConnection; +pub use crate::endpoint::realtime_webrtc::RealtimeSessionConfig; +pub use crate::endpoint::realtime_webrtc::RealtimeWebrtcClient; +pub use crate::endpoint::realtime_webrtc::RealtimeWebrtcConnection; pub use crate::endpoint::responses::ResponsesClient; pub use crate::endpoint::responses::ResponsesOptions; pub use crate::endpoint::responses_websocket::ResponsesWebsocketClient; diff --git a/codex-rs/codex-api/tests/realtime_websocket_e2e.rs b/codex-rs/codex-api/tests/realtime_websocket_e2e.rs deleted file mode 100644 index 0431ff81b1..0000000000 --- a/codex-rs/codex-api/tests/realtime_websocket_e2e.rs +++ /dev/null @@ -1,482 +0,0 @@ -use std::collections::HashMap; -use std::future::Future; -use std::time::Duration; - -use codex_api::RealtimeAudioFrame; -use codex_api::RealtimeEvent; -use codex_api::RealtimeSessionConfig; -use codex_api::RealtimeWebsocketClient; -use codex_api::provider::Provider; -use codex_api::provider::RetryConfig; -use codex_protocol::protocol::RealtimeOutputAudioDelta; -use futures::SinkExt; -use futures::StreamExt; -use http::HeaderMap; -use serde_json::Value; -use serde_json::json; -use tokio::net::TcpListener; -use tokio_tungstenite::accept_async; -use tokio_tungstenite::tungstenite::Message; - -type RealtimeWsStream = tokio_tungstenite::WebSocketStream; - -async fn spawn_realtime_ws_server( - handler: Handler, -) -> (String, tokio::task::JoinHandle<()>) -where - Handler: FnOnce(RealtimeWsStream) -> Fut + Send + 'static, - Fut: Future + Send + 'static, -{ - let listener = match TcpListener::bind("127.0.0.1:0").await { - Ok(listener) => listener, - Err(err) => panic!("failed to bind test websocket listener: {err}"), - }; - let addr = match listener.local_addr() { - Ok(addr) => addr.to_string(), - Err(err) => panic!("failed to read local websocket listener address: {err}"), - }; - - let server = tokio::spawn(async move { - let (stream, _) = match listener.accept().await { - Ok(stream) => stream, - Err(err) => panic!("failed to accept test websocket connection: {err}"), - }; - let ws = match accept_async(stream).await { - Ok(ws) => ws, - Err(err) => panic!("failed to complete websocket handshake: {err}"), - }; - handler(ws).await; - }); - - (addr, server) -} - -fn test_provider(base_url: String) -> Provider { - Provider { - name: "test".to_string(), - base_url, - query_params: Some(HashMap::new()), - headers: HeaderMap::new(), - retry: RetryConfig { - max_attempts: 1, - base_delay: Duration::from_millis(1), - retry_429: false, - retry_5xx: false, - retry_transport: false, - }, - stream_idle_timeout: Duration::from_secs(5), - } -} - -#[tokio::test] -async fn realtime_ws_e2e_session_create_and_event_flow() { - let (addr, server) = spawn_realtime_ws_server(|mut ws: RealtimeWsStream| async move { - let first = ws - .next() - .await - .expect("first msg") - .expect("first msg ok") - .into_text() - .expect("text"); - let first_json: Value = serde_json::from_str(&first).expect("json"); - assert_eq!(first_json["type"], "session.update"); - assert_eq!( - first_json["session"]["type"], - Value::String("realtime".to_string()) - ); - assert_eq!( - first_json["session"]["instructions"], - Value::String("backend prompt".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["format"]["type"], - Value::String("audio/pcm".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["format"]["rate"], - Value::from(24_000) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["noise_reduction"]["type"], - Value::String("near_field".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["turn_detection"]["type"], - Value::String("server_vad".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["turn_detection"]["interrupt_response"], - Value::Bool(true) - ); - assert_eq!( - first_json["session"]["audio"]["input"]["turn_detection"]["create_response"], - Value::Bool(true) - ); - assert_eq!( - first_json["session"]["audio"]["output"]["format"]["type"], - Value::String("audio/pcm".to_string()) - ); - assert_eq!( - first_json["session"]["audio"]["output"]["format"]["rate"], - Value::from(24_000) - ); - assert_eq!( - first_json["session"]["audio"]["output"]["voice"], - Value::String("marin".to_string()) - ); - assert_eq!( - first_json["session"]["tool_choice"], - Value::String("auto".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][0]["type"], - Value::String("function".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][0]["name"], - Value::String("codex".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][0]["parameters"]["properties"]["send_immediately"] - ["type"], - Value::String("boolean".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["type"], - Value::String("function".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["name"], - Value::String("cancel_current_operation".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["parameters"]["type"], - Value::String("object".to_string()) - ); - assert_eq!( - first_json["session"]["tools"][1]["parameters"]["required"], - Value::Array(Vec::new()) - ); - assert_eq!( - first_json["session"]["tools"][1]["parameters"]["properties"], - json!({}) - ); - assert_eq!( - first_json["session"]["tools"] - .as_array() - .expect("tools array") - .iter() - .map(|tool| tool["name"].as_str().expect("tool name")) - .collect::>(), - vec![ - "codex", - "cancel_current_operation", - "turn_off_realtime_mode", - "manage_message_queue", - "manage_runtime_settings", - "run_tui_command", - ] - ); - assert_eq!( - first_json["session"]["tools"][4]["parameters"]["properties"]["working_directory"] - ["type"], - Value::String("string".to_string()) - ); - - ws.send(Message::Text( - json!({ - "type": "session.updated", - "session": {"id": "sess_mock", "instructions": "backend prompt"} - }) - .to_string() - .into(), - )) - .await - .expect("send session.updated"); - - let second = ws - .next() - .await - .expect("second msg") - .expect("second msg ok") - .into_text() - .expect("text"); - let second_json: Value = serde_json::from_str(&second).expect("json"); - assert_eq!(second_json["type"], "input_audio_buffer.append"); - - ws.send(Message::Text( - json!({ - "type": "response.output_audio.delta", - "delta": "AQID", - "sample_rate": 48000, - "channels": 1 - }) - .to_string() - .into(), - )) - .await - .expect("send audio out"); - }) - .await; - - let client = RealtimeWebsocketClient::new(test_provider(format!("http://{addr}"))); - let connection = client - .connect( - RealtimeSessionConfig { - instructions: "backend prompt".to_string(), - model: Some("realtime-test-model".to_string()), - session_id: Some("conv_123".to_string()), - }, - HeaderMap::new(), - HeaderMap::new(), - ) - .await - .expect("connect"); - - let created = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - created, - RealtimeEvent::SessionUpdated { - session_id: "sess_mock".to_string(), - instructions: Some("backend prompt".to_string()), - } - ); - - connection - .send_audio_frame(RealtimeAudioFrame { - data: "AQID".to_string(), - sample_rate: 48000, - num_channels: 1, - samples_per_channel: Some(960), - }) - .await - .expect("send audio"); - - let audio_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - audio_event, - RealtimeEvent::AudioOut(RealtimeOutputAudioDelta { - frame: RealtimeAudioFrame { - data: "AQID".to_string(), - sample_rate: 48000, - num_channels: 1, - samples_per_channel: None, - }, - item_id: None, - }) - ); - - connection.close().await.expect("close"); - server.await.expect("server task"); -} - -#[tokio::test] -async fn realtime_ws_e2e_send_while_next_event_waits() { - let (addr, server) = spawn_realtime_ws_server(|mut ws: RealtimeWsStream| async move { - let first = ws - .next() - .await - .expect("first msg") - .expect("first msg ok") - .into_text() - .expect("text"); - let first_json: Value = serde_json::from_str(&first).expect("json"); - assert_eq!(first_json["type"], "session.update"); - - let second = ws - .next() - .await - .expect("second msg") - .expect("second msg ok") - .into_text() - .expect("text"); - let second_json: Value = serde_json::from_str(&second).expect("json"); - assert_eq!(second_json["type"], "input_audio_buffer.append"); - - ws.send(Message::Text( - json!({ - "type": "session.updated", - "session": {"id": "sess_after_send", "instructions": "backend prompt"} - }) - .to_string() - .into(), - )) - .await - .expect("send session.updated"); - }) - .await; - - let client = RealtimeWebsocketClient::new(test_provider(format!("http://{addr}"))); - let connection = client - .connect( - RealtimeSessionConfig { - instructions: "backend prompt".to_string(), - model: Some("realtime-test-model".to_string()), - session_id: Some("conv_123".to_string()), - }, - HeaderMap::new(), - HeaderMap::new(), - ) - .await - .expect("connect"); - - let (send_result, next_result) = tokio::join!( - async { - tokio::time::timeout( - Duration::from_millis(200), - connection.send_audio_frame(RealtimeAudioFrame { - data: "AQID".to_string(), - sample_rate: 48000, - num_channels: 1, - samples_per_channel: Some(960), - }), - ) - .await - }, - connection.next_event() - ); - - send_result - .expect("send should not block on next_event") - .expect("send audio"); - let next_event = next_result.expect("next event").expect("event"); - assert_eq!( - next_event, - RealtimeEvent::SessionUpdated { - session_id: "sess_after_send".to_string(), - instructions: Some("backend prompt".to_string()), - } - ); - - connection.close().await.expect("close"); - server.await.expect("server task"); -} - -#[tokio::test] -async fn realtime_ws_e2e_disconnected_emitted_once() { - let (addr, server) = spawn_realtime_ws_server(|mut ws: RealtimeWsStream| async move { - let first = ws - .next() - .await - .expect("first msg") - .expect("first msg ok") - .into_text() - .expect("text"); - let first_json: Value = serde_json::from_str(&first).expect("json"); - assert_eq!(first_json["type"], "session.update"); - - ws.send(Message::Close(None)).await.expect("send close"); - }) - .await; - - let client = RealtimeWebsocketClient::new(test_provider(format!("http://{addr}"))); - let connection = client - .connect( - RealtimeSessionConfig { - instructions: "backend prompt".to_string(), - model: Some("realtime-test-model".to_string()), - session_id: Some("conv_123".to_string()), - }, - HeaderMap::new(), - HeaderMap::new(), - ) - .await - .expect("connect"); - - let first = connection.next_event().await.expect("next event"); - assert_eq!(first, None); - - let second = connection.next_event().await.expect("next event"); - assert_eq!(second, None); - - server.await.expect("server task"); -} - -#[tokio::test] -async fn realtime_ws_e2e_forwards_unknown_text_events() { - let (addr, server) = spawn_realtime_ws_server(|mut ws: RealtimeWsStream| async move { - let first = ws - .next() - .await - .expect("first msg") - .expect("first msg ok") - .into_text() - .expect("text"); - let first_json: Value = serde_json::from_str(&first).expect("json"); - assert_eq!(first_json["type"], "session.update"); - - ws.send(Message::Text( - json!({ - "type": "response.created", - "response": {"id": "resp_unknown"} - }) - .to_string() - .into(), - )) - .await - .expect("send unknown event"); - - ws.send(Message::Text( - json!({ - "type": "session.updated", - "session": {"id": "sess_after_unknown", "instructions": "backend prompt"} - }) - .to_string() - .into(), - )) - .await - .expect("send session.updated"); - }) - .await; - - let client = RealtimeWebsocketClient::new(test_provider(format!("http://{addr}"))); - let connection = client - .connect( - RealtimeSessionConfig { - instructions: "backend prompt".to_string(), - model: Some("realtime-test-model".to_string()), - session_id: Some("conv_123".to_string()), - }, - HeaderMap::new(), - HeaderMap::new(), - ) - .await - .expect("connect"); - - let first_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - first_event, - RealtimeEvent::ConversationItemAdded(json!({ - "type": "response.created", - "response": {"id": "resp_unknown"} - })) - ); - - let second_event = connection - .next_event() - .await - .expect("next event") - .expect("event"); - assert_eq!( - second_event, - RealtimeEvent::SessionUpdated { - session_id: "sess_after_unknown".to_string(), - instructions: Some("backend prompt".to_string()), - } - ); - - connection.close().await.expect("close"); - server.await.expect("server task"); -} diff --git a/codex-rs/core/src/realtime_conversation.rs b/codex-rs/core/src/realtime_conversation.rs index 72e33bdcb8..94cb55e66f 100644 --- a/codex-rs/core/src/realtime_conversation.rs +++ b/codex-rs/core/src/realtime_conversation.rs @@ -8,14 +8,12 @@ use crate::error::Result as CodexResult; use crate::realtime_context::build_realtime_startup_context; use async_channel::Receiver; use async_channel::Sender; -use async_channel::TrySendError; use codex_api::Provider as ApiProvider; -use codex_api::RealtimeAudioFrame; use codex_api::RealtimeEvent; use codex_api::RealtimeSessionConfig; -use codex_api::RealtimeWebsocketClient; -use codex_api::endpoint::realtime_websocket::RealtimeWebsocketEvents; -use codex_api::endpoint::realtime_websocket::RealtimeWebsocketWriter; +use codex_api::RealtimeWebrtcClient; +use codex_api::endpoint::realtime_webrtc::RealtimeWebrtcEvents; +use codex_api::endpoint::realtime_webrtc::RealtimeWebrtcWriter; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ConversationAudioParams; use codex_protocol::protocol::ConversationAudioTruncateParams; @@ -24,6 +22,7 @@ use codex_protocol::protocol::ConversationTextParams; use codex_protocol::protocol::ErrorEvent; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; +use codex_protocol::protocol::RealtimeAudioFrame; use codex_protocol::protocol::RealtimeConversationClosedEvent; use codex_protocol::protocol::RealtimeConversationRealtimeEvent; use codex_protocol::protocol::RealtimeConversationStartedEvent; @@ -42,7 +41,6 @@ use tracing::error; use tracing::info; use tracing::warn; -const AUDIO_IN_QUEUE_CAPACITY: usize = 256; const USER_TEXT_IN_QUEUE_CAPACITY: usize = 64; const HANDOFF_OUT_QUEUE_CAPACITY: usize = 64; const OUTPUT_EVENTS_QUEUE_CAPACITY: usize = 256; @@ -132,9 +130,8 @@ impl RealtimeHandoffState { #[allow(dead_code)] struct ConversationState { - audio_tx: Sender, user_text_tx: Sender, - writer: RealtimeWebsocketWriter, + writer: RealtimeWebrtcWriter, handoff: RealtimeHandoffState, task: JoinHandle<()>, realtime_active: Arc, @@ -178,7 +175,7 @@ impl RealtimeConversationManager { model, session_id, }; - let client = RealtimeWebsocketClient::new(api_provider); + let client = RealtimeWebrtcClient::new(api_provider); let connection = client .connect( session_config, @@ -190,8 +187,6 @@ impl RealtimeConversationManager { let writer = connection.writer(); let events = connection.events(); - let (audio_tx, audio_rx) = - async_channel::bounded::(AUDIO_IN_QUEUE_CAPACITY); let (user_text_tx, user_text_rx) = async_channel::bounded::(USER_TEXT_IN_QUEUE_CAPACITY); let (handoff_output_tx, handoff_output_rx) = @@ -206,14 +201,12 @@ impl RealtimeConversationManager { events, user_text_rx, handoff_output_rx, - audio_rx, events_tx, handoff.clone(), ); let mut guard = self.state.lock().await; *guard = Some(ConversationState { - audio_tx, user_text_tx, writer, handoff, @@ -224,27 +217,10 @@ impl RealtimeConversationManager { } pub(crate) async fn audio_in(&self, frame: RealtimeAudioFrame) -> CodexResult<()> { - let sender = { - let guard = self.state.lock().await; - guard.as_ref().map(|state| state.audio_tx.clone()) - }; - - let Some(sender) = sender else { - return Err(CodexErr::InvalidRequest( - "conversation is not running".to_string(), - )); - }; - - match sender.try_send(frame) { - Ok(()) => Ok(()), - Err(TrySendError::Full(_)) => { - warn!("dropping input audio frame due to full queue"); - Ok(()) - } - Err(TrySendError::Closed(_)) => Err(CodexErr::InvalidRequest( - "conversation is not running".to_string(), - )), - } + let _ = frame; + Err(CodexErr::InvalidRequest( + "realtime audio input is handled by WebRTC native audio, not PCM append".to_string(), + )) } pub(crate) async fn text_in(&self, text: String) -> CodexResult<()> { @@ -270,25 +246,17 @@ impl RealtimeConversationManager { &self, params: ConversationAudioTruncateParams, ) -> CodexResult<()> { - let writer = { + let is_running = { let guard = self.state.lock().await; - guard.as_ref().map(|state| state.writer.clone()) + guard.is_some() }; - - let Some(writer) = writer else { + if !is_running { return Err(CodexErr::InvalidRequest( "conversation is not running".to_string(), )); - }; + } - writer - .send_conversation_item_truncate( - params.item_id, - params.content_index, - params.audio_end_ms, - ) - .await - .map_err(map_api_error)?; + let _ = params; Ok(()) } @@ -608,11 +576,10 @@ pub(crate) async fn handle_close(sess: &Arc, sub_id: String) { } fn spawn_realtime_input_task( - writer: RealtimeWebsocketWriter, - events: RealtimeWebsocketEvents, + writer: RealtimeWebrtcWriter, + events: RealtimeWebrtcEvents, user_text_rx: Receiver, handoff_output_rx: Receiver, - audio_rx: Receiver, events_tx: Sender, handoff_state: RealtimeHandoffState, ) -> JoinHandle<()> { @@ -806,18 +773,6 @@ fn spawn_realtime_input_task( } } } - frame = audio_rx.recv() => { - match frame { - Ok(frame) => { - if let Err(err) = writer.send_audio_frame(frame).await { - let mapped_error = map_api_error(err); - error!("failed to send input audio: {mapped_error}"); - break; - } - } - Err(_) => break, - } - } } } }) @@ -843,9 +798,11 @@ async fn send_conversation_error( mod tests { use super::HandoffOutput; use super::INTERRUPT_TOOL_OUTPUT_CANCELLED; + use super::RealtimeConversationManager; use super::RealtimeHandoffState; use super::realtime_text_from_handoff_request; use async_channel::bounded; + use codex_protocol::protocol::RealtimeAudioFrame; use codex_protocol::protocol::RealtimeHandoffRequested; use codex_protocol::protocol::RealtimeTranscriptEntry; use pretty_assertions::assert_eq; @@ -901,6 +858,26 @@ mod tests { assert_eq!(realtime_text_from_handoff_request(&handoff), None); } + #[tokio::test] + async fn rejects_pcm_audio_append_in_webrtc_mode() { + let manager = RealtimeConversationManager::new(); + + let err = manager + .audio_in(RealtimeAudioFrame { + data: "AQID".to_string(), + sample_rate: 24000, + num_channels: 1, + samples_per_channel: Some(480), + }) + .await + .expect_err("PCM append should be unsupported"); + + assert_eq!( + err.to_string(), + "realtime audio input is handled by WebRTC native audio, not PCM append" + ); + } + #[tokio::test] async fn clears_active_handoff_explicitly() { let (tx, _rx) = bounded(1); diff --git a/codex-rs/core/tests/suite/realtime_conversation.rs b/codex-rs/core/tests/suite/realtime_conversation.rs index f7f237742f..6ca948a251 100644 --- a/codex-rs/core/tests/suite/realtime_conversation.rs +++ b/codex-rs/core/tests/suite/realtime_conversation.rs @@ -6,7 +6,6 @@ use codex_core::auth::OPENAI_API_KEY_ENV_VAR; use codex_protocol::ThreadId; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ConversationAudioParams; -use codex_protocol::protocol::ConversationAudioTruncateParams; use codex_protocol::protocol::ConversationStartParams; use codex_protocol::protocol::ConversationTextParams; use codex_protocol::protocol::ErrorEvent; @@ -274,71 +273,6 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> { Ok(()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn conversation_audio_truncate_sends_truncate_event() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = start_websocket_server(vec![ - vec![], - vec![ - vec![json!({ - "type": "session.updated", - "session": { "id": "sess_truncate", "instructions": "backend prompt" } - })], - vec![], - ], - ]) - .await; - - let mut builder = test_codex(); - let test = builder.build_with_websocket_server(&server).await?; - assert!(server.wait_for_handshakes(1, Duration::from_secs(2)).await); - - test.codex - .submit(Op::RealtimeConversationStart(ConversationStartParams { - prompt: "backend prompt".to_string(), - session_id: None, - })) - .await?; - - wait_for_event_match(&test.codex, |msg| match msg { - EventMsg::RealtimeConversationRealtime(RealtimeConversationRealtimeEvent { - payload: RealtimeEvent::SessionUpdated { session_id, .. }, - }) if session_id == "sess_truncate" => Some(()), - _ => None, - }) - .await; - - test.codex - .submit(Op::RealtimeConversationAudioTruncate( - ConversationAudioTruncateParams { - item_id: "item_audio_1".to_string(), - content_index: 0, - audio_end_ms: 320, - }, - )) - .await?; - - let truncate_request = wait_for_matching_websocket_request( - &server, - "realtime conversation.item.truncate request", - |request| request.body_json()["type"].as_str() == Some("conversation.item.truncate"), - ) - .await; - assert_eq!( - truncate_request.body_json(), - json!({ - "type": "conversation.item.truncate", - "item_id": "item_audio_1", - "content_index": 0, - "audio_end_ms": 320 - }) - ); - - server.shutdown().await; - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial(openai_api_key_env)] async fn conversation_start_uses_openai_env_key_fallback_with_chatgpt_auth() -> Result<()> { diff --git a/codex-rs/tui/src/chatwidget/realtime.rs b/codex-rs/tui/src/chatwidget/realtime.rs index 57b9210d98..d270928691 100644 --- a/codex-rs/tui/src/chatwidget/realtime.rs +++ b/codex-rs/tui/src/chatwidget/realtime.rs @@ -1,5 +1,4 @@ use super::*; -use codex_protocol::protocol::ConversationAudioTruncateParams; use codex_protocol::protocol::ConversationStartParams; use codex_protocol::protocol::ConversationTextParams; use codex_protocol::protocol::RealtimeConversationClosedEvent; @@ -538,46 +537,13 @@ impl ChatWidget { } fn enqueue_realtime_audio_out(&mut self, frame: &RealtimeOutputAudioDelta) { - #[cfg(not(target_os = "linux"))] - { - if self.realtime_conversation.audio_player.is_none() { - self.realtime_conversation.audio_player = - crate::voice::RealtimeAudioPlayer::start(&self.config).ok(); - } - if let Some(player) = &self.realtime_conversation.audio_player - && let Err(err) = player.enqueue_frame(frame) - { - warn!("failed to play realtime audio: {err}"); - } - } - #[cfg(target_os = "linux")] - { - let _ = frame; - } + let _ = frame; } - #[cfg(not(target_os = "linux"))] fn interrupt_realtime_audio_playback(&mut self) { - let Some(player) = &self.realtime_conversation.audio_player else { - return; - }; - - let Some(position) = player.interrupt() else { - return; - }; - - self.submit_op(Op::RealtimeConversationAudioTruncate( - ConversationAudioTruncateParams { - item_id: position.item_id, - content_index: 0, - audio_end_ms: position.audio_end_ms, - }, - )); + // WebRTC handles response interruption on the media path. } - #[cfg(target_os = "linux")] - fn interrupt_realtime_audio_playback(&mut self) {} - fn on_realtime_handoff_requested(&mut self, handoff: RealtimeHandoffRequested) { let Some(text) = realtime_text_from_handoff_request(&handoff) else { return; @@ -1467,58 +1433,9 @@ impl ChatWidget { #[cfg(all(not(target_os = "linux"), feature = "voice-input"))] fn start_realtime_local_audio(&mut self) { - if self.realtime_conversation.capture_stop_flag.is_some() { - return; - } - - self.realtime_conversation.meter_generation = - self.realtime_conversation.meter_generation.wrapping_add(1); - let meter_generation = self.realtime_conversation.meter_generation; - self.realtime_conversation.meter_text = Some("тадтадтадтад".to_string()); + self.realtime_conversation.meter_text = None; self.sync_realtime_status_label(); self.request_redraw(); - - let capture = match crate::voice::VoiceCapture::start_realtime( - &self.config, - self.app_event_tx.clone(), - ) { - Ok(capture) => capture, - Err(err) => { - self.realtime_conversation.meter_text = None; - self.sync_realtime_status_label(); - self.add_error_message(format!("Failed to start microphone capture: {err}")); - return; - } - }; - - let stop_flag = capture.stopped_flag(); - let peak = capture.last_peak_arc(); - let app_event_tx = self.app_event_tx.clone(); - - self.realtime_conversation.capture_stop_flag = Some(stop_flag.clone()); - self.realtime_conversation.capture = Some(capture); - if self.realtime_conversation.audio_player.is_none() { - self.realtime_conversation.audio_player = - crate::voice::RealtimeAudioPlayer::start(&self.config).ok(); - } - - std::thread::spawn(move || { - let mut meter = crate::voice::RecordingMeterState::new(); - - loop { - if stop_flag.load(Ordering::Relaxed) { - break; - } - - let meter_text = meter.next_text(peak.load(Ordering::Relaxed)); - app_event_tx.send(AppEvent::UpdateRealtimeRecordingMeter { - generation: meter_generation, - text: meter_text, - }); - - std::thread::sleep(Duration::from_millis(60)); - } - }); } #[cfg(target_os = "linux")] @@ -1529,28 +1446,7 @@ impl ChatWidget { #[cfg(all(not(target_os = "linux"), feature = "voice-input"))] pub(crate) fn restart_realtime_audio_device(&mut self, kind: RealtimeAudioDeviceKind) { - if !self.realtime_conversation.is_active() { - return; - } - - match kind { - RealtimeAudioDeviceKind::Microphone => { - self.stop_realtime_microphone(); - self.start_realtime_local_audio(); - } - RealtimeAudioDeviceKind::Speaker => { - self.stop_realtime_speaker(); - match crate::voice::RealtimeAudioPlayer::start(&self.config) { - Ok(player) => { - self.realtime_conversation.audio_player = Some(player); - } - Err(err) => { - self.add_error_message(format!("Failed to start speaker output: {err}")); - } - } - } - } - self.request_redraw(); + let _ = kind; } #[cfg(any(target_os = "linux", not(feature = "voice-input")))] diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 72e70e2382..bdf41b0618 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -445,7 +445,7 @@ pub async fn run_main(mut cli: Cli, arg0_paths: Arg0DispatchPaths) -> std::io::R let env_filter = || { EnvFilter::try_from_default_env().unwrap_or_else(|_| { EnvFilter::new( - "codex_core=info,codex_tui=info,codex_rmcp_client=info,codex_api::endpoint::realtime_websocket=info", + "codex_core=info,codex_tui=info,codex_rmcp_client=info,codex_api::endpoint::realtime_webrtc=info", ) }) };