From a8ffbe7399f6153c2796b6df1b46c81ee53a9cdd Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 4 May 2026 12:30:44 +0000 Subject: [PATCH 01/11] wip --- Cargo.Bazel.json.lock | 3308 +++++++++++++---- Cargo.Bazel.toml.lock | 470 ++- Cargo.toml | 1 + bazel/rust.MODULE.bazel | 8 + ic-os/components/guestos.bzl | 10 + .../generate-ic-ai-agent-tls-cert.service | 18 + .../ai-agent/generate-ic-ai-agent-tls-cert.sh | 56 + .../guestos/ai-agent/ic-ai-agent-tls.conf | 24 + .../guestos/ai-agent/ic-ai-agent-tls.service | 27 + .../guestos/ai-agent/ic-ai-agent.service | 33 + .../guestos/ollama/manage-ollama.sh | 20 +- ic-os/guestos/context/Dockerfile | 5 +- ic-os/guestos/defs.bzl | 1 + publish/binaries/BUILD.bazel | 1 + rs/ai_agent/BUILD.bazel | 44 + rs/ai_agent/Cargo.toml | 28 + rs/ai_agent/src/config.rs | 62 + rs/ai_agent/src/handlers/chat.rs | 119 + rs/ai_agent/src/handlers/config.rs | 46 + rs/ai_agent/src/handlers/health.rs | 19 + rs/ai_agent/src/handlers/mod.rs | 4 + rs/ai_agent/src/handlers/run.rs | 87 + rs/ai_agent/src/lib.rs | 15 + rs/ai_agent/src/main.rs | 41 + rs/ai_agent/src/models/mod.rs | 7 + rs/ai_agent/src/models/request.rs | 39 + rs/ai_agent/src/models/response.rs | 56 + rs/ai_agent/src/providers/mod.rs | 83 + rs/ai_agent/src/router.rs | 24 + rs/ai_agent/src/state.rs | 27 + rs/ai_agent/src/tools/calculator.rs | 58 + rs/ai_agent/src/tools/current_datetime.rs | 51 + rs/ai_agent/src/tools/mod.rs | 15 + rs/ai_agent/src/tools/registry.rs | 24 + .../config/tool/templates/ic.json5.template | 16 +- 35 files changed, 3962 insertions(+), 885 deletions(-) create mode 100644 ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.service create mode 100755 ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.sh create mode 100644 ic-os/components/guestos/ai-agent/ic-ai-agent-tls.conf create mode 100644 ic-os/components/guestos/ai-agent/ic-ai-agent-tls.service create mode 100644 ic-os/components/guestos/ai-agent/ic-ai-agent.service create mode 100644 rs/ai_agent/BUILD.bazel create mode 100644 rs/ai_agent/Cargo.toml create mode 100644 rs/ai_agent/src/config.rs create mode 100644 rs/ai_agent/src/handlers/chat.rs create mode 100644 rs/ai_agent/src/handlers/config.rs create mode 100644 rs/ai_agent/src/handlers/health.rs create mode 100644 rs/ai_agent/src/handlers/mod.rs create mode 100644 rs/ai_agent/src/handlers/run.rs create mode 100644 rs/ai_agent/src/lib.rs create mode 100644 rs/ai_agent/src/main.rs create mode 100644 rs/ai_agent/src/models/mod.rs create mode 100644 rs/ai_agent/src/models/request.rs create mode 100644 rs/ai_agent/src/models/response.rs create mode 100644 rs/ai_agent/src/providers/mod.rs create mode 100644 rs/ai_agent/src/router.rs create mode 100644 rs/ai_agent/src/state.rs create mode 100644 rs/ai_agent/src/tools/calculator.rs create mode 100644 rs/ai_agent/src/tools/current_datetime.rs create mode 100644 rs/ai_agent/src/tools/mod.rs create mode 100644 rs/ai_agent/src/tools/registry.rs diff --git a/Cargo.Bazel.json.lock b/Cargo.Bazel.json.lock index 92984b8e5b92..5d78613758dd 100644 --- a/Cargo.Bazel.json.lock +++ b/Cargo.Bazel.json.lock @@ -1,5 +1,5 @@ { - "checksum": "973d5e40f08086e83617bf7cc893f4c4fbc17cd1d8798002a0f3c65eec39fe79", + "checksum": "398b1dc766faff551d9d06595a0b31380325851db80ace08da152c1de5fcceb1", "crates": { "abnf 0.12.0": { "name": "abnf", @@ -201,11 +201,11 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { @@ -217,7 +217,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -333,7 +333,7 @@ "target": "flate2" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -389,7 +389,7 @@ "target": "smallvec" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -597,11 +597,11 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -676,11 +676,11 @@ "target": "actix_utils" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -696,7 +696,7 @@ "target": "socket2" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -748,7 +748,7 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -929,11 +929,11 @@ "target": "encoding_rs" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -1132,7 +1132,7 @@ "target": "actix_web" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -2258,7 +2258,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -3864,7 +3864,7 @@ "selects": { "cfg(any())": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -3970,7 +3970,7 @@ "target": "event_listener" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" } ], @@ -4033,7 +4033,7 @@ "target": "event_listener_strategy" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -4106,7 +4106,7 @@ "target": "flate2" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -4118,7 +4118,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -4179,7 +4179,7 @@ "target": "anyhow" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -4251,7 +4251,7 @@ "target": "concurrent_queue" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { @@ -4509,7 +4509,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -4557,7 +4557,7 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -4837,7 +4837,7 @@ "target": "async_net" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -4960,7 +4960,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -5128,6 +5128,204 @@ ], "license_file": "LICENSE-APACHE" }, + "aws-lc-rs 1.16.3": { + "name": "aws-lc-rs", + "version": "1.16.3", + "package_url": "https://github.com/aws/aws-lc-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/aws-lc-rs/1.16.3/download", + "sha256": "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "aws_lc_rs", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "aws_lc_rs", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "aws-lc-sys", + "prebuilt-nasm" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "build_script_build" + }, + { + "id": "aws-lc-sys 0.40.0", + "target": "aws_lc_sys" + }, + { + "id": "zeroize 1.8.1", + "target": "zeroize" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.16.3" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "link_deps": { + "common": [ + { + "id": "aws-lc-sys 0.40.0", + "target": "aws_lc_sys" + } + ], + "selects": {} + }, + "links": "aws_lc_rs_1_16_3_sys" + }, + "license": "ISC AND (Apache-2.0 OR ISC)", + "license_ids": [ + "Apache-2.0", + "ISC" + ], + "license_file": "LICENSE" + }, + "aws-lc-sys 0.40.0": { + "name": "aws-lc-sys", + "version": "0.40.0", + "package_url": "https://github.com/aws/aws-lc-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/aws-lc-sys/0.40.0/download", + "sha256": "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" + } + }, + "targets": [ + { + "Library": { + "crate_name": "aws_lc_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_main", + "crate_root": "builder/main.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "aws_lc_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "prebuilt-nasm" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "aws-lc-sys 0.40.0", + "target": "build_script_main" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.40.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cc 1.2.48", + "target": "cc" + }, + { + "id": "cmake 0.1.58", + "target": "cmake" + }, + { + "id": "dunce 1.0.5", + "target": "dunce" + }, + { + "id": "fs_extra 1.3.0", + "target": "fs_extra" + } + ], + "selects": {} + }, + "links": "aws_lc_0_40_0" + }, + "license": "ISC AND (Apache-2.0 OR ISC) AND Apache-2.0 AND MIT AND BSD-3-Clause AND (Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR ISC OR MIT-0)", + "license_ids": [ + "Apache-2.0", + "BSD-3-Clause", + "ISC", + "MIT", + "MIT-0" + ], + "license_file": "LICENSE" + }, "axum 0.7.9": { "name": "axum", "version": "0.7.9", @@ -5184,7 +5382,7 @@ "target": "bytes" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -5252,7 +5450,7 @@ "target": "sync_wrapper" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -5365,7 +5563,7 @@ "target": "form_urlencoded" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -5437,7 +5635,7 @@ "target": "sync_wrapper" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -5527,7 +5725,7 @@ "target": "bytes" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -5633,7 +5831,7 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -5745,7 +5943,7 @@ "target": "bytes" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -5969,7 +6167,7 @@ "target": "axum" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -6108,7 +6306,7 @@ "target": "rustls_pki_types" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -6174,7 +6372,7 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -6194,7 +6392,7 @@ "target": "rand" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio", "alias": "tokio_1" } @@ -6247,7 +6445,7 @@ "target": "fastrand" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -6255,7 +6453,7 @@ "target": "pin_project" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -6333,7 +6531,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -9065,7 +9263,7 @@ "target": "async_task" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { @@ -10054,7 +10252,7 @@ "target": "cargo_metadata" }, { - "id": "glob 0.3.1", + "id": "glob 0.3.3", "target": "glob" }, { @@ -11063,7 +11261,7 @@ "target": "build_script_build" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -12523,7 +12721,7 @@ "target": "jobserver" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -12533,7 +12731,7 @@ "target": "jobserver" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -12543,7 +12741,7 @@ "target": "jobserver" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -12553,7 +12751,7 @@ "target": "jobserver" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -12730,7 +12928,7 @@ "target": "serde_wasm_bindgen" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], @@ -13236,11 +13434,11 @@ ], "wasm32-unknown-unknown": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], @@ -13625,11 +13823,11 @@ "target": "build_script_build" }, { - "id": "glob 0.3.1", + "id": "glob 0.3.3", "target": "glob" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -13655,7 +13853,7 @@ "deps": { "common": [ { - "id": "glob 0.3.1", + "id": "glob 0.3.3", "target": "glob" } ], @@ -14171,7 +14369,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -14425,6 +14623,54 @@ ], "license_file": null }, + "cmake 0.1.58": { + "name": "cmake", + "version": "0.1.58", + "package_url": "https://github.com/rust-lang/cmake-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/cmake/0.1.58/download", + "sha256": "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" + } + }, + "targets": [ + { + "Library": { + "crate_name": "cmake", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "cmake", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cc 1.2.48", + "target": "cc" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.1.58" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "cobs 0.3.0": { "name": "cobs", "version": "0.3.0", @@ -15128,7 +15374,7 @@ "target": "lazy_static" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -15216,7 +15462,7 @@ "target": "lazy_static" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -15282,7 +15528,7 @@ "target": "cfg_if" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], @@ -15671,7 +15917,7 @@ "target": "core_foundation_sys" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -15730,7 +15976,7 @@ "target": "core_foundation_sys" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -15881,19 +16127,19 @@ "selects": { "aarch64-linux-android": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -16742,7 +16988,7 @@ "selects": { "cfg(target_arch = \"riscv64\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -17030,7 +17276,7 @@ "target": "criterion_plot" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -17078,7 +17324,7 @@ "target": "tinytemplate" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -17641,7 +17887,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -17708,7 +17954,7 @@ "selects": { "aarch64-apple-darwin": [ { - "id": "mio 1.0.2", + "id": "mio 1.2.0", "target": "mio" }, { @@ -17722,7 +17968,7 @@ ], "aarch64-unknown-linux-gnu": [ { - "id": "mio 1.0.2", + "id": "mio 1.2.0", "target": "mio" }, { @@ -17742,7 +17988,7 @@ ], "x86_64-apple-darwin": [ { - "id": "mio 1.0.2", + "id": "mio 1.2.0", "target": "mio" }, { @@ -17756,7 +18002,7 @@ ], "x86_64-unknown-linux-gnu": [ { - "id": "mio 1.0.2", + "id": "mio 1.2.0", "target": "mio" }, { @@ -19762,7 +20008,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -19831,7 +20077,7 @@ "target": "dbus" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -21652,11 +21898,11 @@ "target": "fs_extra" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -21965,7 +22211,7 @@ "target": "leb128" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -22039,6 +22285,10 @@ "id": "metrics-proxy 0.1.0", "target": "metrics_proxy" }, + { + "id": "meval 0.2.0", + "target": "meval" + }, { "id": "minicbor 0.19.1", "target": "minicbor" @@ -22221,7 +22471,7 @@ "target": "quickcheck" }, { - "id": "quinn 0.11.5", + "id": "quinn 0.11.9", "target": "quinn" }, { @@ -22284,6 +22534,11 @@ "id": "rgb 0.8.37", "target": "rgb" }, + { + "id": "rig-core 0.36.0", + "target": "rig", + "alias": "rig_core" + }, { "id": "ring 0.17.14", "target": "ring" @@ -22566,7 +22821,7 @@ "target": "time" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -22626,7 +22881,7 @@ "target": "tower" }, { - "id": "tower-http 0.6.6", + "id": "tower-http 0.6.8", "target": "tower_http" }, { @@ -22714,7 +22969,7 @@ "target": "warp" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" }, { @@ -23104,7 +23359,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -23171,7 +23426,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -23233,7 +23488,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -25549,19 +25804,19 @@ ], "cfg(target_os = \"hermit\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(target_os = \"wasi\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -25624,19 +25879,19 @@ "selects": { "cfg(target_os = \"hermit\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(target_os = \"wasi\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -25706,7 +25961,7 @@ "target": "build_script_build" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -26463,6 +26718,69 @@ ], "license_file": "LICENSE-APACHE" }, + "eventsource-stream 0.2.3": { + "name": "eventsource-stream", + "version": "0.2.3", + "package_url": "https://github.com/jpopesculian/eventsource-stream", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/eventsource-stream/0.2.3/download", + "sha256": "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" + } + }, + "targets": [ + { + "Library": { + "crate_name": "eventsource_stream", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "eventsource_stream", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "futures-core 0.3.32", + "target": "futures_core" + }, + { + "id": "nom 7.1.3", + "target": "nom" + }, + { + "id": "pin-project-lite 0.2.16", + "target": "pin_project_lite" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.2.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, "evm_rpc_client 0.4.0": { "name": "evm_rpc_client", "version": "0.4.0", @@ -26658,7 +26976,7 @@ "target": "errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -27292,7 +27610,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -27401,7 +27719,7 @@ "target": "build_script_build" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -28504,7 +28822,7 @@ "target": "build_script_build" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -28704,14 +29022,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures 0.3.31": { + "futures 0.3.32": { "name": "futures", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures/0.3.31/download", - "sha256": "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" + "url": "https://static.crates.io/crates/futures/0.3.32/download", + "sha256": "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" } }, "targets": [ @@ -28747,38 +29065,38 @@ "deps": { "common": [ { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-executor 0.3.31", + "id": "futures-executor 0.3.32", "target": "futures_executor" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { - "id": "futures-task 0.3.31", + "id": "futures-task 0.3.32", "target": "futures_task" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" } ], "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -28787,14 +29105,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-channel 0.3.31": { + "futures-channel 0.3.32": { "name": "futures-channel", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-channel/0.3.31/download", - "sha256": "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" + "url": "https://static.crates.io/crates/futures-channel/0.3.32/download", + "sha256": "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" } }, "targets": [ @@ -28829,18 +29147,18 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" } ], "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -28849,14 +29167,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-core 0.3.31": { + "futures-core 0.3.32": { "name": "futures-core", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-core/0.3.31/download", - "sha256": "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + "url": "https://static.crates.io/crates/futures-core/0.3.32/download", + "sha256": "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" } }, "targets": [ @@ -28887,7 +29205,7 @@ "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -28896,14 +29214,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-executor 0.3.31": { + "futures-executor 0.3.32": { "name": "futures-executor", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-executor/0.3.31/download", - "sha256": "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" + "url": "https://static.crates.io/crates/futures-executor/0.3.32/download", + "sha256": "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" } }, "targets": [ @@ -28935,22 +29253,22 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-task 0.3.31", + "id": "futures-task 0.3.32", "target": "futures_task" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" } ], "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -28959,14 +29277,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-io 0.3.31": { + "futures-io 0.3.32": { "name": "futures-io", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-io/0.3.31/download", - "sha256": "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + "url": "https://static.crates.io/crates/futures-io/0.3.32/download", + "sha256": "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" } }, "targets": [ @@ -28996,7 +29314,7 @@ "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -29055,11 +29373,11 @@ "target": "fastrand" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { @@ -29091,14 +29409,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-macro 0.3.31": { + "futures-macro 0.3.32": { "name": "futures-macro", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-macro/0.3.31/download", - "sha256": "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" + "url": "https://static.crates.io/crates/futures-macro/0.3.32/download", + "sha256": "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" } }, "targets": [ @@ -29138,7 +29456,7 @@ "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -29186,7 +29504,7 @@ "deps": { "common": [ { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { @@ -29211,14 +29529,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-sink 0.3.31": { + "futures-sink 0.3.32": { "name": "futures-sink", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-sink/0.3.31/download", - "sha256": "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + "url": "https://static.crates.io/crates/futures-sink/0.3.32/download", + "sha256": "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" } }, "targets": [ @@ -29249,7 +29567,7 @@ "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -29258,14 +29576,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-task 0.3.31": { + "futures-task 0.3.32": { "name": "futures-task", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-task/0.3.31/download", - "sha256": "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + "url": "https://static.crates.io/crates/futures-task/0.3.32/download", + "sha256": "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" } }, "targets": [ @@ -29290,12 +29608,13 @@ "crate_features": { "common": [ "alloc", + "default", "std" ], "selects": {} }, "edition": "2018", - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -29343,14 +29662,14 @@ ], "license_file": "LICENSE-APACHE" }, - "futures-util 0.3.31": { + "futures-util 0.3.32": { "name": "futures-util", - "version": "0.3.31", + "version": "0.3.32", "package_url": "https://github.com/rust-lang/futures-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/futures-util/0.3.31/download", - "sha256": "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" + "url": "https://static.crates.io/crates/futures-util/0.3.32/download", + "sha256": "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" } }, "targets": [ @@ -29394,23 +29713,23 @@ "deps": { "common": [ { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { - "id": "futures-task 0.3.31", + "id": "futures-task 0.3.32", "target": "futures_task" }, { @@ -29421,10 +29740,6 @@ "id": "pin-project-lite 0.2.16", "target": "pin_project_lite" }, - { - "id": "pin-utils 0.1.0", - "target": "pin_utils" - }, { "id": "slab 0.4.8", "target": "slab" @@ -29436,13 +29751,13 @@ "proc_macro_deps": { "common": [ { - "id": "futures-macro 0.3.31", + "id": "futures-macro 0.3.32", "target": "futures_macro" } ], "selects": {} }, - "version": "0.3.31" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -29700,7 +30015,7 @@ ], "cfg(all(not(windows), not(any(target_os = \"android\", target_os = \"linux\"))))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -29828,7 +30143,13 @@ "custom", "std" ], - "selects": {} + "selects": { + "wasm32-unknown-unknown": [ + "js", + "js-sys", + "wasm-bindgen" + ] + } }, "deps": { "common": [ @@ -29846,9 +30167,19 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } + ], + "wasm32-unknown-unknown": [ + { + "id": "js-sys 0.3.97", + "target": "js_sys" + }, + { + "id": "wasm-bindgen 0.2.120", + "target": "wasm_bindgen" + } ] } }, @@ -29924,7 +30255,7 @@ "selects": { "cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(getrandom_backend = \"custom\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -29942,49 +30273,49 @@ ], "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", all(target_os = \"horizon\", target_arch = \"arm\")))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(target_os = \"netbsd\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(target_os = \"solaris\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(target_os = \"vxworks\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "wasm32-unknown-unknown": [ { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ] @@ -30244,14 +30575,14 @@ ], "license_file": "LICENSE-APACHE" }, - "glob 0.3.1": { + "glob 0.3.3": { "name": "glob", - "version": "0.3.1", + "version": "0.3.3", "package_url": "https://github.com/rust-lang/glob", "repository": { "Http": { - "url": "https://static.crates.io/crates/glob/0.3.1/download", - "sha256": "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + "url": "https://static.crates.io/crates/glob/0.3.3/download", + "sha256": "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" } }, "targets": [ @@ -30274,7 +30605,7 @@ "**" ], "edition": "2015", - "version": "0.3.1" + "version": "0.3.3" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -30393,7 +30724,7 @@ "target": "dashmap" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { @@ -30401,7 +30732,7 @@ "target": "futures_timer" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -30506,7 +30837,7 @@ "target": "dashmap" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { @@ -30514,7 +30845,7 @@ "target": "futures_timer" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -30787,15 +31118,15 @@ "target": "fnv" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -30811,7 +31142,7 @@ "target": "slab" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -30880,15 +31211,15 @@ "target": "fnv" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -30904,7 +31235,7 @@ "target": "slab" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -31943,7 +32274,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -32398,15 +32729,15 @@ "target": "data_encoding" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -32458,7 +32789,7 @@ "target": "tinyvec" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -32554,7 +32885,7 @@ "target": "cfg_if" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -32594,7 +32925,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -32819,7 +33150,7 @@ "selects": { "cfg(any(unix, target_os = \"redox\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -32885,7 +33216,7 @@ "selects": { "cfg(any(unix, target_os = \"redox\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -33372,7 +33703,7 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -33658,15 +33989,15 @@ "target": "bytes" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -33702,7 +34033,7 @@ "target": "socket2" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -33780,11 +34111,11 @@ "target": "bytes" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -33824,7 +34155,7 @@ "target": "smallvec" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -33889,7 +34220,7 @@ "deps": { "common": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -33913,7 +34244,7 @@ "target": "rustls_native_certs" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -33978,15 +34309,19 @@ ], "selects": { "aarch64-apple-darwin": [ + "aws-lc-rs", "webpki-tokio" ], "aarch64-unknown-linux-gnu": [ + "aws-lc-rs", "webpki-tokio" ], "x86_64-apple-darwin": [ + "aws-lc-rs", "webpki-tokio" ], "x86_64-unknown-linux-gnu": [ + "aws-lc-rs", "webpki-tokio" ] } @@ -34027,7 +34362,7 @@ "target": "rustls_platform_verifier" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -34108,7 +34443,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -34172,7 +34507,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -34238,16 +34573,20 @@ ], "selects": { "aarch64-apple-darwin": [ - "client-proxy" + "client-proxy", + "client-proxy-system" ], "aarch64-unknown-linux-gnu": [ - "client-proxy" + "client-proxy", + "client-proxy-system" ], "x86_64-apple-darwin": [ - "client-proxy" + "client-proxy", + "client-proxy-system" ], "x86_64-unknown-linux-gnu": [ - "client-proxy" + "client-proxy", + "client-proxy-system" ] } }, @@ -34258,11 +34597,11 @@ "target": "bytes" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -34278,7 +34617,7 @@ "target": "hyper" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -34290,7 +34629,7 @@ "target": "socket2" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -34315,6 +34654,10 @@ { "id": "percent-encoding 2.3.1", "target": "percent_encoding" + }, + { + "id": "system-configuration 0.6.1", + "target": "system_configuration" } ], "aarch64-unknown-linux-gnu": [ @@ -34343,6 +34686,10 @@ { "id": "percent-encoding 2.3.1", "target": "percent_encoding" + }, + { + "id": "system-configuration 0.6.1", + "target": "system_configuration" } ], "x86_64-unknown-linux-gnu": [ @@ -34416,11 +34763,11 @@ ], "cfg(target_arch = \"wasm32\")": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], @@ -34623,7 +34970,7 @@ "target": "elliptic_curve" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -34738,7 +35085,7 @@ "selects": { "cfg(not(target_family = \"wasm\"))": [ { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ] @@ -34867,11 +35214,11 @@ "target": "fqdn" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -35019,7 +35366,7 @@ "target": "sha2" }, { - "id": "socket2 0.6.1", + "id": "socket2 0.6.3", "target": "socket2" }, { @@ -35043,7 +35390,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -35247,7 +35594,7 @@ "target": "serde" }, { - "id": "socket2 0.6.1", + "id": "socket2 0.6.3", "target": "socket2" }, { @@ -36271,7 +36618,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -36279,7 +36626,7 @@ "target": "tokio_util" }, { - "id": "tower-http 0.6.6", + "id": "tower-http 0.6.8", "target": "tower_http" }, { @@ -36415,7 +36762,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -36587,7 +36934,7 @@ "target": "ic_custom_domains_canister_api" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -36906,7 +37253,7 @@ "target": "fqdn" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -37022,7 +37369,7 @@ "target": "time" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -37034,7 +37381,7 @@ "target": "tower" }, { - "id": "tower-http 0.6.6", + "id": "tower-http 0.6.8", "target": "tower_http" }, { @@ -37210,7 +37557,7 @@ "target": "candid" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -38098,7 +38445,7 @@ "target": "candid" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -38288,7 +38635,7 @@ "target": "candid" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -38852,7 +39199,7 @@ "target": "candid" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -41132,7 +41479,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -42282,7 +42629,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -42298,14 +42645,14 @@ ], "license_file": "LICENSE-APACHE" }, - "js-sys 0.3.77": { + "js-sys 0.3.97": { "name": "js-sys", - "version": "0.3.77", - "package_url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/js-sys", + "version": "0.3.97", + "package_url": "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/js-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/js-sys/0.3.77/download", - "sha256": "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" + "url": "https://static.crates.io/crates/js-sys/0.3.97/download", + "sha256": "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" } }, "targets": [ @@ -42330,25 +42677,34 @@ "crate_features": { "common": [ "default", - "std" + "std", + "unsafe-eval" ], "selects": {} }, "deps": { "common": [ + { + "id": "cfg-if 1.0.0", + "target": "cfg_if" + }, + { + "id": "futures-util 0.3.32", + "target": "futures_util" + }, { "id": "once_cell 1.21.3", "target": "once_cell" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], "selects": {} }, "edition": "2021", - "version": "0.3.77" + "version": "0.3.97" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -44126,14 +44482,14 @@ ], "license_file": "LICENSE-APACHE" }, - "libc 0.2.177": { + "libc 0.2.186": { "name": "libc", - "version": "0.2.177", + "version": "0.2.186", "package_url": "https://github.com/rust-lang/libc", "repository": { "Http": { - "url": "https://static.crates.io/crates/libc/0.2.177/download", - "sha256": "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + "url": "https://static.crates.io/crates/libc/0.2.186/download", + "sha256": "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" } }, "targets": [ @@ -44178,14 +44534,14 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "build_script_build" } ], "selects": {} }, "edition": "2021", - "version": "0.2.177" + "version": "0.2.186" }, "build_script_attrs": { "compile_data_glob": [ @@ -44269,7 +44625,7 @@ "target": "either" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -44991,7 +45347,7 @@ "target": "lazy_static" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -45051,7 +45407,7 @@ "target": "bitflags" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -45117,7 +45473,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -45151,7 +45507,7 @@ "target": "cc" }, { - "id": "glob 0.3.1", + "id": "glob 0.3.3", "target": "glob" } ], @@ -45323,7 +45679,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -45460,7 +45816,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -45547,7 +45903,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -45649,7 +46005,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -45783,7 +46139,7 @@ "target": "bitflags" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -46035,7 +46391,7 @@ "deps": { "common": [ { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -46103,7 +46459,7 @@ "target": "byteorder" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -46181,7 +46537,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -46270,15 +46626,15 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -46343,7 +46699,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -46850,7 +47206,7 @@ "target": "errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -47029,6 +47385,46 @@ ], "license_file": "LICENSE-APACHE" }, + "lru-slab 0.1.2": { + "name": "lru-slab", + "version": "0.1.2", + "package_url": "https://github.com/Ralith/lru-slab", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/lru-slab/0.1.2/download", + "sha256": "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + } + }, + "targets": [ + { + "Library": { + "crate_name": "lru_slab", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "lru_slab", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.1.2" + }, + "license": "MIT OR Apache-2.0 OR Zlib", + "license_ids": [ + "Apache-2.0", + "MIT", + "Zlib" + ], + "license_file": "LICENSE-APACHE" + }, "lz4_flex 0.11.5": { "name": "lz4_flex", "version": "0.11.5", @@ -47137,7 +47533,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -47307,7 +47703,7 @@ "selects": { "cfg(any(target_os = \"macos\", target_os = \"ios\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -48009,7 +48405,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -48059,7 +48455,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -48376,7 +48772,7 @@ "target": "exitcode" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -48468,7 +48864,7 @@ "target": "simple_logger" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -48497,6 +48893,64 @@ "license_ids": [], "license_file": "LICENSE" }, + "meval 0.2.0": { + "name": "meval", + "version": "0.2.0", + "package_url": "https://github.com/rekka/meval-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/meval/0.2.0/download", + "sha256": "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9" + } + }, + "targets": [ + { + "Library": { + "crate_name": "meval", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "meval", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "fnv 1.0.7", + "target": "fnv" + }, + { + "id": "nom 1.2.4", + "target": "nom" + } + ], + "selects": {} + }, + "edition": "2015", + "version": "0.2.0" + }, + "license": "Unlicense/MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, "mime 0.3.17": { "name": "mime", "version": "0.3.17", @@ -48536,14 +48990,14 @@ ], "license_file": "LICENSE-APACHE" }, - "mime_guess 2.0.4": { + "mime_guess 2.0.5": { "name": "mime_guess", - "version": "2.0.4", + "version": "2.0.5", "package_url": "https://github.com/abonander/mime_guess", "repository": { "Http": { - "url": "https://static.crates.io/crates/mime_guess/2.0.4/download", - "sha256": "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" + "url": "https://static.crates.io/crates/mime_guess/2.0.5/download", + "sha256": "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" } }, "targets": [ @@ -48591,7 +49045,7 @@ "target": "mime" }, { - "id": "mime_guess 2.0.4", + "id": "mime_guess 2.0.5", "target": "build_script_build" }, { @@ -48602,7 +49056,7 @@ "selects": {} }, "edition": "2015", - "version": "2.0.4" + "version": "2.0.5" }, "build_script_attrs": { "compile_data_glob": [ @@ -48994,7 +49448,7 @@ "selects": { "cfg(target_os = \"wasi\")": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -49004,7 +49458,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -49025,14 +49479,14 @@ ], "license_file": "LICENSE" }, - "mio 1.0.2": { + "mio 1.2.0": { "name": "mio", - "version": "1.0.2", + "version": "1.2.0", "package_url": "https://github.com/tokio-rs/mio", "repository": { "Http": { - "url": "https://static.crates.io/crates/mio/1.0.2/download", - "sha256": "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" + "url": "https://static.crates.io/crates/mio/1.2.0/download", + "sha256": "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" } }, "targets": [ @@ -49094,32 +49548,21 @@ "target": "log" } ], - "cfg(target_os = \"hermit\")": [ + "cfg(any(unix, target_os = \"hermit\", target_os = \"wasi\"))": [ { - "id": "hermit-abi 0.3.9", - "target": "hermit_abi", - "alias": "libc" + "id": "libc 0.2.186", + "target": "libc" } ], "cfg(target_os = \"wasi\")": [ - { - "id": "libc 0.2.177", - "target": "libc" - }, { "id": "wasi 0.11.0+wasi-snapshot-preview1", "target": "wasi" } ], - "cfg(unix)": [ - { - "id": "libc 0.2.177", - "target": "libc" - } - ], "cfg(windows)": [ { - "id": "windows-sys 0.52.0", + "id": "windows-sys 0.61.2", "target": "windows_sys" } ], @@ -49138,7 +49581,7 @@ } }, "edition": "2021", - "version": "1.0.2" + "version": "1.2.0" }, "license": "MIT", "license_ids": [ @@ -49359,7 +49802,7 @@ "target": "colored" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -49407,7 +49850,7 @@ "target": "similar" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -49489,7 +49932,7 @@ "target": "event_listener" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -49624,7 +50067,7 @@ "target": "encoding_rs" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -49828,6 +50271,53 @@ ], "license_file": "LICENSE" }, + "nanoid 0.4.0": { + "name": "nanoid", + "version": "0.4.0", + "package_url": "https://github.com/nikolay-govorov/nanoid.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/nanoid/0.4.0/download", + "sha256": "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" + } + }, + "targets": [ + { + "Library": { + "crate_name": "nanoid", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "nanoid", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "rand 0.8.5", + "target": "rand" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.4.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "neli 0.6.4": { "name": "neli", "version": "0.6.4", @@ -49870,7 +50360,7 @@ "target": "byteorder" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -50151,7 +50641,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -50218,7 +50708,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -50282,7 +50772,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -50380,7 +50870,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -50481,7 +50971,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -50611,6 +51101,51 @@ ], "license_file": "LICENSE-APACHE" }, + "nom 1.2.4": { + "name": "nom", + "version": "1.2.4", + "package_url": "https://github.com/Geal/nom", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/nom/1.2.4/download", + "sha256": "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" + } + }, + "targets": [ + { + "Library": { + "crate_name": "nom", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "nom", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "stream" + ], + "selects": {} + }, + "edition": "2015", + "version": "1.2.4" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "nom 7.1.3": { "name": "nom", "version": "7.1.3", @@ -51988,7 +52523,7 @@ "selects": { "cfg(not(windows))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -52168,7 +52703,7 @@ "selects": { "cfg(any(target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -52338,7 +52873,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -53202,7 +53737,7 @@ "target": "foreign_types" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -53447,7 +53982,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -53608,11 +54143,11 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { @@ -53639,7 +54174,7 @@ "selects": { "cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" } ] @@ -53696,11 +54231,11 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { @@ -53719,7 +54254,7 @@ "selects": { "cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" } ] @@ -53782,7 +54317,7 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -53810,7 +54345,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -54069,11 +54604,11 @@ "deps": { "common": [ { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -54096,7 +54631,7 @@ "selects": { "cfg(target_arch = \"wasm32\")": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" } ] @@ -54158,15 +54693,15 @@ "target": "crossbeam_channel" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-executor 0.3.31", + "id": "futures-executor 0.3.32", "target": "futures_executor" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -54262,19 +54797,19 @@ "target": "crossbeam_channel" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-executor 0.3.31", + "id": "futures-executor 0.3.32", "target": "futures_executor" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { - "id": "glob 0.3.1", + "id": "glob 0.3.3", "target": "glob" }, { @@ -54302,7 +54837,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -54381,19 +54916,19 @@ "deps": { "common": [ { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-executor 0.3.31", + "id": "futures-executor 0.3.32", "target": "futures_executor" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { - "id": "glob 0.3.1", + "id": "glob 0.3.3", "target": "glob" }, { @@ -54417,7 +54952,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -54541,6 +55076,60 @@ ], "license_file": "LICENSE-MIT" }, + "ordered-float 5.3.0": { + "name": "ordered-float", + "version": "5.3.0", + "package_url": "https://github.com/reem/rust-ordered-float", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/ordered-float/5.3.0/download", + "sha256": "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ordered_float", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ordered_float", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "num_traits" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "5.3.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE-MIT" + }, "ordered-multimap 0.7.3": { "name": "ordered-multimap", "version": "0.7.3", @@ -55326,7 +55915,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -55424,7 +56013,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -55776,7 +56365,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -55844,7 +56433,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -56121,7 +56710,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -57354,7 +57943,7 @@ "target": "fastrand" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" } ], @@ -57701,11 +58290,11 @@ "selects": { "cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))": [ { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" }, { - "id": "web-sys 0.3.64", + "id": "web-sys 0.3.97", "target": "web_sys" } ] @@ -57913,7 +58502,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -58469,7 +59058,7 @@ "target": "inferno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -60293,7 +60882,7 @@ "target": "lazy_static" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -60418,7 +61007,7 @@ "target": "lazy_static" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -62287,7 +62876,7 @@ "selects": { "cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))": [ { - "id": "web-sys 0.3.64", + "id": "web-sys 0.3.97", "target": "web_sys" } ], @@ -62299,7 +62888,7 @@ ], "cfg(not(any(target_os = \"windows\", target_arch = \"wasm32\")))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -62531,14 +63120,14 @@ ], "license_file": "LICENSE-MIT" }, - "quinn 0.11.5": { + "quinn 0.11.9": { "name": "quinn", - "version": "0.11.5", + "version": "0.11.9", "package_url": "https://github.com/quinn-rs/quinn", "repository": { "Http": { - "url": "https://static.crates.io/crates/quinn/0.11.5/download", - "sha256": "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" + "url": "https://static.crates.io/crates/quinn/0.11.9/download", + "sha256": "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" } }, "targets": [ @@ -62553,6 +63142,18 @@ ] } } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } } ], "library_target_name": "quinn", @@ -62565,7 +63166,8 @@ "log", "ring", "runtime-tokio", - "rustls" + "rustls", + "rustls-ring" ], "selects": {} }, @@ -62580,7 +63182,11 @@ "target": "pin_project_lite" }, { - "id": "quinn-proto 0.11.7", + "id": "quinn 0.11.9", + "target": "build_script_build" + }, + { + "id": "quinn-proto 0.11.14", "target": "quinn_proto", "alias": "proto" }, @@ -62598,15 +63204,11 @@ "target": "rustls" }, { - "id": "socket2 0.5.9", - "target": "socket2" - }, - { - "id": "thiserror 1.0.68", + "id": "thiserror 2.0.18", "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -62614,10 +63216,43 @@ "target": "tracing" } ], - "selects": {} + "selects": { + "cfg(all(target_family = \"wasm\", target_os = \"unknown\"))": [ + { + "id": "web-time 1.1.0", + "target": "web_time" + } + ], + "cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))": [ + { + "id": "socket2 0.5.9", + "target": "socket2" + } + ] + } }, "edition": "2021", - "version": "0.11.5" + "version": "0.11.9" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cfg_aliases 0.2.1", + "target": "cfg_aliases" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -62626,14 +63261,14 @@ ], "license_file": "LICENSE-APACHE" }, - "quinn-proto 0.11.7": { + "quinn-proto 0.11.14": { "name": "quinn-proto", - "version": "0.11.7", + "version": "0.11.14", "package_url": "https://github.com/quinn-rs/quinn", "repository": { "Http": { - "url": "https://static.crates.io/crates/quinn-proto/0.11.7/download", - "sha256": "ea0a9b3a42929fad8a7c3de7f86ce0814cfa893328157672680e9fb1145549c5" + "url": "https://static.crates.io/crates/quinn-proto/0.11.14/download", + "sha256": "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" } }, "targets": [ @@ -62659,7 +63294,7 @@ "common": [ "log", "ring", - "rustls" + "rustls-ring" ], "selects": {} }, @@ -62670,7 +63305,11 @@ "target": "bytes" }, { - "id": "rand 0.8.5", + "id": "lru-slab 0.1.2", + "target": "lru_slab" + }, + { + "id": "rand 0.9.0", "target": "rand" }, { @@ -62690,7 +63329,7 @@ "target": "slab" }, { - "id": "thiserror 1.0.68", + "id": "thiserror 2.0.18", "target": "thiserror" }, { @@ -62702,10 +63341,25 @@ "target": "tracing" } ], - "selects": {} + "selects": { + "cfg(all(target_family = \"wasm\", target_os = \"unknown\"))": [ + { + "id": "getrandom 0.3.1", + "target": "getrandom" + }, + { + "id": "rustls-pki-types 1.12.0", + "target": "rustls_pki_types" + }, + { + "id": "web-time 1.1.0", + "target": "web_time" + } + ] + } }, "edition": "2021", - "version": "0.11.7" + "version": "0.11.14" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -62754,7 +63408,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -63079,7 +63733,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -63177,25 +63831,25 @@ "selects": { "aarch64-apple-darwin": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "aarch64-unknown-linux-gnu": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "x86_64-apple-darwin": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "x86_64-unknown-linux-gnu": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -63886,7 +64540,7 @@ "selects": { "cfg(any(target_os = \"macos\", target_os = \"ios\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -63965,7 +64619,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -66045,11 +66699,11 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -66184,25 +66838,25 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], "cfg(target_arch = \"wasm32\")": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" }, { - "id": "wasm-bindgen-futures 0.4.37", + "id": "wasm-bindgen-futures 0.4.70", "target": "wasm_bindgen_futures" }, { - "id": "web-sys 0.3.64", + "id": "web-sys 0.3.97", "target": "web_sys" } ], @@ -66352,11 +67006,11 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -66364,7 +67018,7 @@ "target": "http" }, { - "id": "mime_guess 2.0.4", + "id": "mime_guess 2.0.5", "target": "mime_guess" }, { @@ -66395,7 +67049,7 @@ "target": "async_compression" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { @@ -66445,7 +67099,7 @@ "target": "async_compression" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { @@ -66519,7 +67173,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -66527,7 +67181,7 @@ "target": "tower" }, { - "id": "tower-http 0.6.6", + "id": "tower-http 0.6.8", "target": "tower_http" }, { @@ -66537,19 +67191,19 @@ ], "cfg(target_arch = \"wasm32\")": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" }, { - "id": "wasm-bindgen-futures 0.4.37", + "id": "wasm-bindgen-futures 0.4.70", "target": "wasm_bindgen_futures" }, { - "id": "web-sys 0.3.64", + "id": "web-sys 0.3.97", "target": "web_sys" } ], @@ -66565,7 +67219,7 @@ "target": "async_compression" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { @@ -66615,7 +67269,7 @@ "target": "async_compression" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { @@ -66671,6 +67325,328 @@ ], "license_file": "LICENSE-APACHE" }, + "reqwest 0.13.3": { + "name": "reqwest", + "version": "0.13.3", + "package_url": "https://github.com/seanmonstar/reqwest", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/reqwest/0.13.3/download", + "sha256": "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" + } + }, + "targets": [ + { + "Library": { + "crate_name": "reqwest", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "reqwest", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "__rustls", + "__rustls-aws-lc-rs", + "__tls", + "charset", + "http2", + "json", + "multipart", + "rustls", + "stream", + "system-proxy" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "base64 0.22.1", + "target": "base64" + }, + { + "id": "bytes 1.11.1", + "target": "bytes" + }, + { + "id": "futures-core 0.3.32", + "target": "futures_core" + }, + { + "id": "futures-util 0.3.32", + "target": "futures_util" + }, + { + "id": "http 1.3.1", + "target": "http" + }, + { + "id": "mime_guess 2.0.5", + "target": "mime_guess" + }, + { + "id": "serde 1.0.228", + "target": "serde" + }, + { + "id": "serde_json 1.0.145", + "target": "serde_json" + }, + { + "id": "sync_wrapper 1.0.2", + "target": "sync_wrapper" + }, + { + "id": "url 2.5.4", + "target": "url" + } + ], + "selects": { + "aarch64-apple-darwin": [ + { + "id": "encoding_rs 0.8.32", + "target": "encoding_rs" + }, + { + "id": "h2 0.4.4", + "target": "h2" + }, + { + "id": "hyper-rustls 0.27.7", + "target": "hyper_rustls" + }, + { + "id": "mime 0.3.17", + "target": "mime" + }, + { + "id": "rustls 0.23.27", + "target": "rustls" + }, + { + "id": "rustls-pki-types 1.12.0", + "target": "rustls_pki_types" + }, + { + "id": "rustls-platform-verifier 0.6.2", + "target": "rustls_platform_verifier" + }, + { + "id": "tokio-rustls 0.26.0", + "target": "tokio_rustls" + }, + { + "id": "tokio-util 0.7.17", + "target": "tokio_util" + } + ], + "aarch64-unknown-linux-gnu": [ + { + "id": "encoding_rs 0.8.32", + "target": "encoding_rs" + }, + { + "id": "h2 0.4.4", + "target": "h2" + }, + { + "id": "hyper-rustls 0.27.7", + "target": "hyper_rustls" + }, + { + "id": "mime 0.3.17", + "target": "mime" + }, + { + "id": "rustls 0.23.27", + "target": "rustls" + }, + { + "id": "rustls-pki-types 1.12.0", + "target": "rustls_pki_types" + }, + { + "id": "rustls-platform-verifier 0.6.2", + "target": "rustls_platform_verifier" + }, + { + "id": "tokio-rustls 0.26.0", + "target": "tokio_rustls" + }, + { + "id": "tokio-util 0.7.17", + "target": "tokio_util" + } + ], + "cfg(all(target_arch = \"wasm32\", any(target_os = \"unknown\", target_os = \"none\")))": [ + { + "id": "js-sys 0.3.97", + "target": "js_sys" + }, + { + "id": "wasm-bindgen 0.2.120", + "target": "wasm_bindgen" + }, + { + "id": "wasm-bindgen-futures 0.4.70", + "target": "wasm_bindgen_futures" + }, + { + "id": "web-sys 0.3.97", + "target": "web_sys" + } + ], + "cfg(not(all(target_arch = \"wasm32\", any(target_os = \"unknown\", target_os = \"none\"))))": [ + { + "id": "http-body 1.0.1", + "target": "http_body" + }, + { + "id": "http-body-util 0.1.3", + "target": "http_body_util" + }, + { + "id": "hyper 1.8.1", + "target": "hyper" + }, + { + "id": "hyper-util 0.1.12", + "target": "hyper_util" + }, + { + "id": "log 0.4.28", + "target": "log" + }, + { + "id": "percent-encoding 2.3.1", + "target": "percent_encoding" + }, + { + "id": "pin-project-lite 0.2.16", + "target": "pin_project_lite" + }, + { + "id": "tokio 1.52.1", + "target": "tokio" + }, + { + "id": "tower 0.5.2", + "target": "tower" + }, + { + "id": "tower-http 0.6.8", + "target": "tower_http" + }, + { + "id": "tower-service 0.3.3", + "target": "tower_service" + } + ], + "wasm32-unknown-unknown": [ + { + "id": "wasm-streams 0.5.0", + "target": "wasm_streams" + } + ], + "x86_64-apple-darwin": [ + { + "id": "encoding_rs 0.8.32", + "target": "encoding_rs" + }, + { + "id": "h2 0.4.4", + "target": "h2" + }, + { + "id": "hyper-rustls 0.27.7", + "target": "hyper_rustls" + }, + { + "id": "mime 0.3.17", + "target": "mime" + }, + { + "id": "rustls 0.23.27", + "target": "rustls" + }, + { + "id": "rustls-pki-types 1.12.0", + "target": "rustls_pki_types" + }, + { + "id": "rustls-platform-verifier 0.6.2", + "target": "rustls_platform_verifier" + }, + { + "id": "tokio-rustls 0.26.0", + "target": "tokio_rustls" + }, + { + "id": "tokio-util 0.7.17", + "target": "tokio_util" + } + ], + "x86_64-unknown-linux-gnu": [ + { + "id": "encoding_rs 0.8.32", + "target": "encoding_rs" + }, + { + "id": "h2 0.4.4", + "target": "h2" + }, + { + "id": "hyper-rustls 0.27.7", + "target": "hyper_rustls" + }, + { + "id": "mime 0.3.17", + "target": "mime" + }, + { + "id": "rustls 0.23.27", + "target": "rustls" + }, + { + "id": "rustls-pki-types 1.12.0", + "target": "rustls_pki_types" + }, + { + "id": "rustls-platform-verifier 0.6.2", + "target": "rustls_platform_verifier" + }, + { + "id": "tokio-rustls 0.26.0", + "target": "tokio_rustls" + }, + { + "id": "tokio-util 0.7.17", + "target": "tokio_util" + } + ] + } + }, + "edition": "2021", + "version": "0.13.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "resolv-conf 0.7.0": { "name": "resolv-conf", "version": "0.7.0", @@ -67001,6 +67977,153 @@ ], "license_file": "LICENSE" }, + "rig-core 0.36.0": { + "name": "rig-core", + "version": "0.36.0", + "package_url": "https://github.com/0xPlaygrounds/rig", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rig-core/0.36.0/download", + "sha256": "9ac7d75266ac1f79b1faeff611c85cb40be00a0a983db10036591424f0433dc1" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rig", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rig", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "reqwest", + "rustls" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "as-any 0.3.2", + "target": "as_any" + }, + { + "id": "async-stream 0.3.6", + "target": "async_stream" + }, + { + "id": "base64 0.22.1", + "target": "base64" + }, + { + "id": "bytes 1.11.1", + "target": "bytes" + }, + { + "id": "eventsource-stream 0.2.3", + "target": "eventsource_stream" + }, + { + "id": "fastrand 2.3.0", + "target": "fastrand" + }, + { + "id": "futures 0.3.32", + "target": "futures" + }, + { + "id": "futures-timer 3.0.3", + "target": "futures_timer" + }, + { + "id": "glob 0.3.3", + "target": "glob" + }, + { + "id": "http 1.3.1", + "target": "http" + }, + { + "id": "mime 0.3.17", + "target": "mime" + }, + { + "id": "mime_guess 2.0.5", + "target": "mime_guess" + }, + { + "id": "nanoid 0.4.0", + "target": "nanoid" + }, + { + "id": "ordered-float 5.3.0", + "target": "ordered_float" + }, + { + "id": "pin-project-lite 0.2.16", + "target": "pin_project_lite" + }, + { + "id": "reqwest 0.13.3", + "target": "reqwest" + }, + { + "id": "schemars 1.1.0", + "target": "schemars" + }, + { + "id": "serde 1.0.228", + "target": "serde" + }, + { + "id": "serde_json 1.0.145", + "target": "serde_json" + }, + { + "id": "thiserror 2.0.18", + "target": "thiserror" + }, + { + "id": "tokio 1.52.1", + "target": "tokio" + }, + { + "id": "tracing 0.1.41", + "target": "tracing" + }, + { + "id": "tracing-futures 0.2.5", + "target": "tracing_futures" + }, + { + "id": "url 2.5.4", + "target": "url" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "0.36.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "ring 0.16.20": { "name": "ring", "version": "0.16.20", @@ -67071,7 +68194,7 @@ ], "cfg(all(target_arch = \"wasm32\", target_vendor = \"unknown\", target_os = \"unknown\", target_env = \"\"))": [ { - "id": "web-sys 0.3.64", + "id": "web-sys 0.3.97", "target": "web_sys" } ], @@ -67083,7 +68206,7 @@ ], "cfg(any(target_os = \"android\", target_os = \"linux\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -67189,7 +68312,11 @@ "dev_urandom_fallback", "std" ], - "selects": {} + "selects": { + "wasm32-unknown-unknown": [ + "wasm32_unknown_unknown_js" + ] + } }, "deps": { "common": [ @@ -67219,13 +68346,13 @@ ], "cfg(all(all(target_arch = \"aarch64\", target_endian = \"little\"), target_vendor = \"apple\", any(target_os = \"ios\", target_os = \"macos\", target_os = \"tvos\", target_os = \"visionos\", target_os = \"watchos\")))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(all(any(all(target_arch = \"aarch64\", target_endian = \"little\"), all(target_arch = \"arm\", target_endian = \"little\")), any(target_os = \"android\", target_os = \"linux\")))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -67785,7 +68912,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -67999,7 +69126,7 @@ "deps": { "common": [ { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -68082,7 +69209,7 @@ "target": "cfg_if" }, { - "id": "glob 0.3.1", + "id": "glob 0.3.3", "target": "glob" }, { @@ -68190,7 +69317,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -68889,7 +70016,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -68912,7 +70039,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -68934,7 +70061,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -68945,7 +70072,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -69062,7 +70189,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -69085,7 +70212,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -69107,7 +70234,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -69118,7 +70245,7 @@ "alias": "libc_errno" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -69429,7 +70556,24 @@ "std", "tls12" ], - "selects": {} + "selects": { + "aarch64-apple-darwin": [ + "aws-lc-rs", + "aws_lc_rs" + ], + "aarch64-unknown-linux-gnu": [ + "aws-lc-rs", + "aws_lc_rs" + ], + "x86_64-apple-darwin": [ + "aws-lc-rs", + "aws_lc_rs" + ], + "x86_64-unknown-linux-gnu": [ + "aws-lc-rs", + "aws_lc_rs" + ] + } }, "deps": { "common": [ @@ -69475,7 +70619,32 @@ "target": "zeroize" } ], - "selects": {} + "selects": { + "aarch64-apple-darwin": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "aarch64-unknown-linux-gnu": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "x86_64-apple-darwin": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "x86_64-unknown-linux-gnu": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ] + } }, "edition": "2021", "version": "0.23.27" @@ -69497,7 +70666,32 @@ "target": "ring" } ], - "selects": {} + "selects": { + "aarch64-apple-darwin": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "aarch64-unknown-linux-gnu": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "x86_64-apple-darwin": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "x86_64-unknown-linux-gnu": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ] + } } }, "license": "Apache-2.0 OR ISC OR MIT", @@ -69568,7 +70762,7 @@ "target": "chrono" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -69919,7 +71113,12 @@ "default", "std" ], - "selects": {} + "selects": { + "wasm32-unknown-unknown": [ + "web", + "web-time" + ] + } }, "deps": { "common": [ @@ -69928,7 +71127,14 @@ "target": "zeroize" } ], - "selects": {} + "selects": { + "wasm32-unknown-unknown": [ + { + "id": "web-time 1.1.0", + "target": "web_time" + } + ] + } }, "edition": "2021", "version": "1.12.0" @@ -70252,7 +71458,20 @@ "ring", "std" ], - "selects": {} + "selects": { + "aarch64-apple-darwin": [ + "aws-lc-rs" + ], + "aarch64-unknown-linux-gnu": [ + "aws-lc-rs" + ], + "x86_64-apple-darwin": [ + "aws-lc-rs" + ], + "x86_64-unknown-linux-gnu": [ + "aws-lc-rs" + ] + } }, "deps": { "common": [ @@ -70270,7 +71489,32 @@ "target": "untrusted" } ], - "selects": {} + "selects": { + "aarch64-apple-darwin": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "aarch64-unknown-linux-gnu": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "x86_64-apple-darwin": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ], + "x86_64-unknown-linux-gnu": [ + { + "id": "aws-lc-rs 1.16.3", + "target": "aws_lc_rs" + } + ] + } }, "edition": "2021", "version": "0.103.3" @@ -70984,6 +72228,15 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "default", + "derive", + "schemars_derive", + "std" + ], + "selects": {} + }, "deps": { "common": [ { @@ -71006,6 +72259,15 @@ "selects": {} }, "edition": "2021", + "proc_macro_deps": { + "common": [ + { + "id": "schemars_derive 1.1.0", + "target": "schemars_derive" + } + ], + "selects": {} + }, "version": "1.1.0" }, "license": "MIT", @@ -71073,6 +72335,65 @@ ], "license_file": "LICENSE" }, + "schemars_derive 1.1.0": { + "name": "schemars_derive", + "version": "1.1.0", + "package_url": "https://github.com/GREsau/schemars", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/schemars_derive/1.1.0/download", + "sha256": "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "schemars_derive", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "schemars_derive", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.103", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.42", + "target": "quote" + }, + { + "id": "serde_derive_internals 0.29.1", + "target": "serde_derive_internals" + }, + { + "id": "syn 2.0.110", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "scoped-tls 1.0.1": { "name": "scoped-tls", "version": "1.0.1", @@ -72029,7 +73350,7 @@ "target": "core_foundation_sys" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -72100,7 +73421,7 @@ "target": "core_foundation_sys" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -72166,7 +73487,7 @@ "target": "core_foundation_sys" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -72545,7 +73866,7 @@ "deps": { "common": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { @@ -72553,7 +73874,7 @@ "target": "serde" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], @@ -73973,7 +75294,7 @@ "target": "lazy_static" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -74307,7 +75628,7 @@ "target": "sha2" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -74621,7 +75942,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -74697,7 +76018,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -74706,7 +76027,7 @@ "alias": "mio_0_8" }, { - "id": "mio 1.0.2", + "id": "mio 1.2.0", "target": "mio", "alias": "mio_1_0" }, @@ -74759,7 +76080,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -76511,7 +77832,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -76573,7 +77894,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -76595,14 +77916,14 @@ ], "license_file": "LICENSE-APACHE" }, - "socket2 0.6.1": { + "socket2 0.6.3": { "name": "socket2", - "version": "0.6.1", + "version": "0.6.3", "package_url": "https://github.com/rust-lang/socket2", "repository": { "Http": { - "url": "https://static.crates.io/crates/socket2/0.6.1/download", - "sha256": "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" + "url": "https://static.crates.io/crates/socket2/0.6.3/download", + "sha256": "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" } }, "targets": [ @@ -76633,22 +77954,22 @@ "deps": { "common": [], "selects": { - "cfg(unix)": [ + "cfg(any(unix, target_os = \"wasi\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], "cfg(windows)": [ { - "id": "windows-sys 0.60.2", + "id": "windows-sys 0.61.2", "target": "windows_sys" } ] } }, "edition": "2021", - "version": "0.6.1" + "version": "0.6.3" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -76715,7 +78036,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -77023,7 +78344,7 @@ "target": "bitflags" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -77142,7 +78463,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -77272,7 +78593,7 @@ "target": "cfg_if" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -78114,7 +79435,7 @@ "target": "rand" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -78618,7 +79939,7 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" } ], @@ -78739,7 +80060,7 @@ "target": "bitflags" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -78936,7 +80257,7 @@ ], "cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -79017,6 +80338,62 @@ ], "license_file": null }, + "system-configuration 0.6.1": { + "name": "system-configuration", + "version": "0.6.1", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration/0.6.1/download", + "sha256": "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "bitflags 2.10.0", + "target": "bitflags" + }, + { + "id": "core-foundation 0.9.4", + "target": "core_foundation" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "system_configuration_sys" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.6.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "system-configuration-sys 0.5.0": { "name": "system-configuration-sys", "version": "0.5.0", @@ -79065,7 +80442,7 @@ "target": "core_foundation_sys" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -79096,6 +80473,85 @@ ], "license_file": null }, + "system-configuration-sys 0.6.0": { + "name": "system-configuration-sys", + "version": "0.6.0", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration-sys/0.6.0/download", + "sha256": "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "core-foundation-sys 0.8.7", + "target": "core_foundation_sys" + }, + { + "id": "libc 0.2.186", + "target": "libc" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.6.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "systemd 0.10.0": { "name": "systemd", "version": "0.10.0", @@ -79144,7 +80600,7 @@ "target": "foreign_types" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -79215,7 +80671,7 @@ "target": "lazy_static" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -79555,7 +81011,7 @@ ], "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -79715,7 +81171,7 @@ "target": "fnv" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -79747,7 +81203,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -80278,7 +81734,7 @@ "selects": { "cfg(not(windows))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -80332,7 +81788,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -80481,7 +81937,7 @@ "target": "getopts" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81100,7 +82556,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81181,7 +82637,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81277,7 +82733,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81370,7 +82826,7 @@ "selects": { "aarch64-apple-darwin": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81380,7 +82836,7 @@ ], "aarch64-unknown-linux-gnu": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81390,7 +82846,7 @@ ], "x86_64-apple-darwin": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81400,7 +82856,7 @@ ], "x86_64-unknown-linux-gnu": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -81616,7 +83072,7 @@ "selects": { "cfg(target_arch = \"wasm32\")": [ { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ] @@ -82046,14 +83502,14 @@ ], "license_file": null }, - "tokio 1.48.0": { + "tokio 1.52.1": { "name": "tokio", - "version": "1.48.0", + "version": "1.52.1", "package_url": "https://github.com/tokio-rs/tokio", "repository": { "Http": { - "url": "https://static.crates.io/crates/tokio/1.48.0/download", - "sha256": "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" + "url": "https://static.crates.io/crates/tokio/1.52.1/download", + "sha256": "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" } }, "targets": [ @@ -82109,7 +83565,7 @@ "target": "bytes" }, { - "id": "mio 1.0.2", + "id": "mio 1.2.0", "target": "mio" }, { @@ -82124,7 +83580,7 @@ "selects": { "aarch64-apple-darwin": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -82132,13 +83588,13 @@ "target": "signal_hook_registry" }, { - "id": "socket2 0.6.1", + "id": "socket2 0.6.3", "target": "socket2" } ], "aarch64-unknown-linux-gnu": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -82146,13 +83602,13 @@ "target": "signal_hook_registry" }, { - "id": "socket2 0.6.1", + "id": "socket2 0.6.3", "target": "socket2" } ], "x86_64-apple-darwin": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -82160,13 +83616,13 @@ "target": "signal_hook_registry" }, { - "id": "socket2 0.6.1", + "id": "socket2 0.6.3", "target": "socket2" } ], "x86_64-unknown-linux-gnu": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -82174,7 +83630,7 @@ "target": "signal_hook_registry" }, { - "id": "socket2 0.6.1", + "id": "socket2 0.6.3", "target": "socket2" } ] @@ -82184,13 +83640,13 @@ "proc_macro_deps": { "common": [ { - "id": "tokio-macros 2.6.0", + "id": "tokio-macros 2.7.0", "target": "tokio_macros" } ], "selects": {} }, - "version": "1.48.0" + "version": "1.52.1" }, "license": "MIT", "license_ids": [ @@ -82234,7 +83690,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -82250,14 +83706,14 @@ ], "license_file": "LICENSE-APACHE" }, - "tokio-macros 2.6.0": { + "tokio-macros 2.7.0": { "name": "tokio-macros", - "version": "2.6.0", + "version": "2.7.0", "package_url": "https://github.com/tokio-rs/tokio", "repository": { "Http": { - "url": "https://static.crates.io/crates/tokio-macros/2.6.0/download", - "sha256": "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" + "url": "https://static.crates.io/crates/tokio-macros/2.7.0/download", + "sha256": "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" } }, "targets": [ @@ -82297,7 +83753,7 @@ "selects": {} }, "edition": "2021", - "version": "2.6.0" + "version": "2.7.0" }, "license": "MIT", "license_ids": [ @@ -82345,7 +83801,7 @@ "deps": { "common": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -82353,7 +83809,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -82418,7 +83874,7 @@ "target": "rusqlite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -82477,7 +83933,7 @@ "target": "rustls" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -82543,7 +83999,7 @@ "alias": "pki_types" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -82595,7 +84051,24 @@ "ring", "tls12" ], - "selects": {} + "selects": { + "aarch64-apple-darwin": [ + "aws-lc-rs", + "aws_lc_rs" + ], + "aarch64-unknown-linux-gnu": [ + "aws-lc-rs", + "aws_lc_rs" + ], + "x86_64-apple-darwin": [ + "aws-lc-rs", + "aws_lc_rs" + ], + "x86_64-unknown-linux-gnu": [ + "aws-lc-rs", + "aws_lc_rs" + ] + } }, "deps": { "common": [ @@ -82609,7 +84082,7 @@ "alias": "pki_types" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -82677,11 +84150,11 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { @@ -82761,7 +84234,7 @@ "target": "either" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -82769,7 +84242,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -82824,7 +84297,7 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -82832,7 +84305,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -82887,11 +84360,11 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -82951,7 +84424,7 @@ "deps": { "common": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -82959,7 +84432,7 @@ "target": "log" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -82978,6 +84451,65 @@ ], "license_file": "LICENSE" }, + "tokio-tungstenite 0.23.1": { + "name": "tokio-tungstenite", + "version": "0.23.1", + "package_url": "https://github.com/snapview/tokio-tungstenite", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/tokio-tungstenite/0.23.1/download", + "sha256": "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" + } + }, + "targets": [ + { + "Library": { + "crate_name": "tokio_tungstenite", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "tokio_tungstenite", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "futures-util 0.3.32", + "target": "futures_util" + }, + { + "id": "log 0.4.28", + "target": "log" + }, + { + "id": "tokio 1.52.1", + "target": "tokio" + }, + { + "id": "tungstenite 0.23.0", + "target": "tungstenite" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.23.1" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "tokio-tungstenite 0.26.2": { "name": "tokio-tungstenite", "version": "0.26.2", @@ -83019,7 +84551,7 @@ "deps": { "common": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -83027,7 +84559,7 @@ "target": "log" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -83101,19 +84633,19 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { - "id": "futures-sink 0.3.31", + "id": "futures-sink 0.3.32", "target": "futures_sink" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -83129,7 +84661,7 @@ "target": "slab" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" } ], @@ -83467,7 +84999,7 @@ "target": "socket2" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -83602,7 +85134,7 @@ "target": "pin_project" }, { - "id": "socket2 0.6.1", + "id": "socket2 0.6.3", "target": "socket2" }, { @@ -83610,7 +85142,7 @@ "target": "sync_wrapper" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -83789,11 +85321,11 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -83817,7 +85349,7 @@ "target": "slab" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -83914,11 +85446,11 @@ "deps": { "common": [ { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -83942,7 +85474,7 @@ "target": "sync_wrapper" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -84039,7 +85571,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -84066,14 +85598,14 @@ ], "license_file": "LICENSE" }, - "tower-http 0.6.6": { + "tower-http 0.6.8": { "name": "tower-http", - "version": "0.6.6", + "version": "0.6.8", "package_url": "https://github.com/tower-rs/tower-http", "repository": { "Http": { - "url": "https://static.crates.io/crates/tower-http/0.6.6/download", - "sha256": "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" + "url": "https://static.crates.io/crates/tower-http/0.6.8/download", + "sha256": "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" } }, "targets": [ @@ -84155,7 +85687,7 @@ "target": "bytes" }, { - "id": "futures-core 0.3.31", + "id": "futures-core 0.3.32", "target": "futures_core" }, { @@ -84175,7 +85707,7 @@ "target": "pin_project_lite" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -84206,7 +85738,7 @@ "selects": { "aarch64-apple-darwin": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -84216,7 +85748,7 @@ ], "aarch64-unknown-linux-gnu": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -84226,7 +85758,7 @@ ], "x86_64-apple-darwin": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -84236,7 +85768,7 @@ ], "x86_64-unknown-linux-gnu": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -84247,7 +85779,7 @@ } }, "edition": "2018", - "version": "0.6.6" + "version": "0.6.8" }, "license": "MIT", "license_ids": [ @@ -84422,7 +85954,7 @@ "deps": { "common": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -84430,7 +85962,7 @@ "target": "pin_project" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -84933,6 +86465,77 @@ ], "license_file": "LICENSE" }, + "tracing-futures 0.2.5": { + "name": "tracing-futures", + "version": "0.2.5", + "package_url": "https://github.com/tokio-rs/tracing", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/tracing-futures/0.2.5/download", + "sha256": "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "tracing_futures", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "tracing_futures", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "futures", + "futures-03", + "futures-task", + "pin-project", + "std", + "std-future" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "futures 0.3.32", + "target": "futures" + }, + { + "id": "futures-task 0.3.32", + "target": "futures_task" + }, + { + "id": "pin-project 1.1.2", + "target": "pin_project" + }, + { + "id": "tracing 0.1.41", + "target": "tracing" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.2.5" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "tracing-journald 0.3.2": { "name": "tracing-journald", "version": "0.3.2", @@ -84965,7 +86568,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -85189,7 +86792,7 @@ "selects": { "cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { @@ -85659,15 +87262,15 @@ "target": "data_encoding" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-io 0.3.31", + "id": "futures-io 0.3.32", "target": "futures_io" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -85699,7 +87302,7 @@ "target": "tinyvec" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -85783,7 +87386,7 @@ "target": "cfg_if" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -85811,7 +87414,7 @@ "target": "thiserror" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -86035,6 +87638,74 @@ ], "license_file": "LICENSE-APACHE" }, + "tungstenite 0.23.0": { + "name": "tungstenite", + "version": "0.23.0", + "package_url": "https://github.com/snapview/tungstenite-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/tungstenite/0.23.0/download", + "sha256": "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" + } + }, + "targets": [ + { + "Library": { + "crate_name": "tungstenite", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "tungstenite", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "byteorder 1.5.0", + "target": "byteorder" + }, + { + "id": "bytes 1.11.1", + "target": "bytes" + }, + { + "id": "log 0.4.28", + "target": "log" + }, + { + "id": "rand 0.8.5", + "target": "rand" + }, + { + "id": "thiserror 1.0.68", + "target": "thiserror" + }, + { + "id": "utf-8 0.7.6", + "target": "utf8" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.23.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "tungstenite 0.26.2": { "name": "tungstenite", "version": "0.26.2", @@ -86167,7 +87838,7 @@ "target": "bytes" }, { - "id": "futures 0.3.31", + "id": "futures 0.3.32", "target": "futures" }, { @@ -86187,7 +87858,7 @@ "target": "scoped_tls" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -87980,7 +89651,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -88047,7 +89718,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -88322,7 +89993,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -88375,7 +90046,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -88863,11 +90534,11 @@ "target": "bytes" }, { - "id": "futures-channel 0.3.31", + "id": "futures-channel 0.3.32", "target": "futures_channel" }, { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { @@ -88891,7 +90562,7 @@ "target": "mime" }, { - "id": "mime_guess 2.0.4", + "id": "mime_guess 2.0.5", "target": "mime_guess" }, { @@ -88927,7 +90598,7 @@ "target": "serde_urlencoded" }, { - "id": "tokio 1.48.0", + "id": "tokio 1.52.1", "target": "tokio" }, { @@ -89049,14 +90720,14 @@ ], "license_file": "LICENSE-APACHE" }, - "wasm-bindgen 0.2.100": { + "wasm-bindgen 0.2.120": { "name": "wasm-bindgen", - "version": "0.2.100", - "package_url": "https://github.com/rustwasm/wasm-bindgen", + "version": "0.2.120", + "package_url": "https://github.com/wasm-bindgen/wasm-bindgen", "repository": { "Http": { - "url": "https://static.crates.io/crates/wasm-bindgen/0.2.100/download", - "sha256": "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" + "url": "https://static.crates.io/crates/wasm-bindgen/0.2.120/download", + "sha256": "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" } }, "targets": [ @@ -89093,8 +90764,6 @@ "crate_features": { "common": [ "default", - "msrv", - "rustversion", "std" ], "selects": {} @@ -89110,8 +90779,12 @@ "target": "once_cell" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "build_script_build" + }, + { + "id": "wasm-bindgen-shared 0.2.120", + "target": "wasm_bindgen_shared" } ], "selects": {} @@ -89120,17 +90793,13 @@ "proc_macro_deps": { "common": [ { - "id": "rustversion 1.0.14", - "target": "rustversion" - }, - { - "id": "wasm-bindgen-macro 0.2.100", + "id": "wasm-bindgen-macro 0.2.120", "target": "wasm_bindgen_macro" } ], "selects": {} }, - "version": "0.2.100" + "version": "0.2.120" }, "build_script_attrs": { "compile_data_glob": [ @@ -89141,75 +90810,26 @@ ], "data_glob": [ "**" - ] - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "wasm-bindgen-backend 0.2.100": { - "name": "wasm-bindgen-backend", - "version": "0.2.100", - "package_url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/wasm-bindgen-backend/0.2.100/download", - "sha256": "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" - } - }, - "targets": [ - { - "Library": { - "crate_name": "wasm_bindgen_backend", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "wasm_bindgen_backend", - "common_attrs": { - "compile_data_glob": [ - "**" ], - "deps": { + "link_deps": { "common": [ { - "id": "bumpalo 3.20.2", - "target": "bumpalo" - }, - { - "id": "log 0.4.28", - "target": "log" - }, - { - "id": "proc-macro2 1.0.103", - "target": "proc_macro2" - }, - { - "id": "quote 1.0.42", - "target": "quote" - }, - { - "id": "syn 2.0.110", - "target": "syn" - }, - { - "id": "wasm-bindgen-shared 0.2.100", + "id": "wasm-bindgen-shared 0.2.120", "target": "wasm_bindgen_shared" } ], "selects": {} }, - "edition": "2021", - "version": "0.2.100" + "proc_macro_deps": { + "common": [ + { + "id": "rustversion 1.0.14", + "target": "rustversion", + "alias": "rustversion_compat" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -89218,14 +90838,14 @@ ], "license_file": "LICENSE-APACHE" }, - "wasm-bindgen-futures 0.4.37": { + "wasm-bindgen-futures 0.4.70": { "name": "wasm-bindgen-futures", - "version": "0.4.37", - "package_url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures", + "version": "0.4.70", + "package_url": "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/futures", "repository": { "Http": { - "url": "https://static.crates.io/crates/wasm-bindgen-futures/0.4.37/download", - "sha256": "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" + "url": "https://static.crates.io/crates/wasm-bindgen-futures/0.4.70/download", + "sha256": "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" } }, "targets": [ @@ -89247,48 +90867,44 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, "deps": { "common": [ { - "id": "cfg-if 1.0.0", - "target": "cfg_if" - }, - { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], - "selects": { - "cfg(target_feature = \"atomics\")": [ - { - "id": "web-sys 0.3.64", - "target": "web_sys" - } - ] - } + "selects": {} }, - "edition": "2018", - "version": "0.4.37" + "edition": "2021", + "version": "0.4.70" }, - "license": "MIT/Apache-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], "license_file": "LICENSE-APACHE" }, - "wasm-bindgen-macro 0.2.100": { + "wasm-bindgen-macro 0.2.120": { "name": "wasm-bindgen-macro", - "version": "0.2.100", - "package_url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro", + "version": "0.2.120", + "package_url": "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro", "repository": { "Http": { - "url": "https://static.crates.io/crates/wasm-bindgen-macro/0.2.100/download", - "sha256": "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" + "url": "https://static.crates.io/crates/wasm-bindgen-macro/0.2.120/download", + "sha256": "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" } }, "targets": [ @@ -89317,14 +90933,14 @@ "target": "quote" }, { - "id": "wasm-bindgen-macro-support 0.2.100", + "id": "wasm-bindgen-macro-support 0.2.120", "target": "wasm_bindgen_macro_support" } ], "selects": {} }, "edition": "2021", - "version": "0.2.100" + "version": "0.2.120" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -89333,14 +90949,14 @@ ], "license_file": "LICENSE-APACHE" }, - "wasm-bindgen-macro-support 0.2.100": { + "wasm-bindgen-macro-support 0.2.120": { "name": "wasm-bindgen-macro-support", - "version": "0.2.100", - "package_url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support", + "version": "0.2.120", + "package_url": "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro-support", "repository": { "Http": { - "url": "https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.100/download", - "sha256": "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" + "url": "https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.120/download", + "sha256": "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" } }, "targets": [ @@ -89364,6 +90980,10 @@ ], "deps": { "common": [ + { + "id": "bumpalo 3.20.2", + "target": "bumpalo" + }, { "id": "proc-macro2 1.0.103", "target": "proc_macro2" @@ -89377,18 +90997,14 @@ "target": "syn" }, { - "id": "wasm-bindgen-backend 0.2.100", - "target": "wasm_bindgen_backend" - }, - { - "id": "wasm-bindgen-shared 0.2.100", + "id": "wasm-bindgen-shared 0.2.120", "target": "wasm_bindgen_shared" } ], "selects": {} }, "edition": "2021", - "version": "0.2.100" + "version": "0.2.120" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -89397,14 +91013,14 @@ ], "license_file": "LICENSE-APACHE" }, - "wasm-bindgen-shared 0.2.100": { + "wasm-bindgen-shared 0.2.120": { "name": "wasm-bindgen-shared", - "version": "0.2.100", - "package_url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared", + "version": "0.2.120", + "package_url": "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/shared", "repository": { "Http": { - "url": "https://static.crates.io/crates/wasm-bindgen-shared/0.2.100/download", - "sha256": "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" + "url": "https://static.crates.io/crates/wasm-bindgen-shared/0.2.120/download", + "sha256": "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" } }, "targets": [ @@ -89445,14 +91061,14 @@ "target": "unicode_ident" }, { - "id": "wasm-bindgen-shared 0.2.100", + "id": "wasm-bindgen-shared 0.2.120", "target": "build_script_build" } ], "selects": {} }, "edition": "2021", - "version": "0.2.100" + "version": "0.2.120" }, "build_script_attrs": { "compile_data_glob": [ @@ -89873,23 +91489,23 @@ "deps": { "common": [ { - "id": "futures-util 0.3.31", + "id": "futures-util 0.3.32", "target": "futures_util" }, { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" }, { - "id": "wasm-bindgen-futures 0.4.37", + "id": "wasm-bindgen-futures 0.4.70", "target": "wasm_bindgen_futures" }, { - "id": "web-sys 0.3.64", + "id": "web-sys 0.3.97", "target": "web_sys" } ], @@ -89905,6 +91521,70 @@ ], "license_file": "LICENSE-APACHE" }, + "wasm-streams 0.5.0": { + "name": "wasm-streams", + "version": "0.5.0", + "package_url": "https://github.com/MattiasBuelens/wasm-streams/", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/wasm-streams/0.5.0/download", + "sha256": "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" + } + }, + "targets": [ + { + "Library": { + "crate_name": "wasm_streams", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "wasm_streams", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "futures-util 0.3.32", + "target": "futures_util" + }, + { + "id": "js-sys 0.3.97", + "target": "js_sys" + }, + { + "id": "wasm-bindgen 0.2.120", + "target": "wasm_bindgen" + }, + { + "id": "wasm-bindgen-futures 0.4.70", + "target": "wasm_bindgen_futures" + }, + { + "id": "web-sys 0.3.97", + "target": "web_sys" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.5.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "wasmparser 0.212.0": { "name": "wasmparser", "version": "0.212.0", @@ -90523,7 +92203,7 @@ "target": "cfg_if" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -91137,7 +92817,7 @@ "selects": { "cfg(unix)": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -91345,7 +93025,7 @@ "selects": { "cfg(any(target_os = \"linux\", target_vendor = \"apple\", target_os = \"freebsd\", target_os = \"android\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -91796,14 +93476,14 @@ ], "license_file": null }, - "web-sys 0.3.64": { + "web-sys 0.3.97": { "name": "web-sys", - "version": "0.3.64", - "package_url": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys", + "version": "0.3.97", + "package_url": "https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/web-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/web-sys/0.3.64/download", - "sha256": "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" + "url": "https://static.crates.io/crates/web-sys/0.3.97/download", + "sha256": "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" } }, "targets": [ @@ -91874,27 +93554,29 @@ "WorkerGlobalScope", "WritableStream", "WritableStreamDefaultController", - "WritableStreamDefaultWriter" + "WritableStreamDefaultWriter", + "default", + "std" ], "selects": {} }, "deps": { "common": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ], "selects": {} }, - "edition": "2018", - "version": "0.3.64" + "edition": "2021", + "version": "0.3.97" }, - "license": "MIT/Apache-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" @@ -91935,11 +93617,11 @@ "selects": { "cfg(all(target_family = \"wasm\", target_os = \"unknown\"))": [ { - "id": "js-sys 0.3.77", + "id": "js-sys 0.3.97", "target": "js_sys" }, { - "id": "wasm-bindgen 0.2.100", + "id": "wasm-bindgen 0.2.120", "target": "wasm_bindgen" } ] @@ -92173,7 +93855,7 @@ "target": "either" }, { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ], @@ -93205,6 +94887,62 @@ ], "license_file": "license-apache-2.0" }, + "windows-registry 0.4.0": { + "name": "windows-registry", + "version": "0.4.0", + "package_url": "https://github.com/microsoft/windows-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/windows-registry/0.4.0/download", + "sha256": "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" + } + }, + "targets": [ + { + "Library": { + "crate_name": "windows_registry", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "windows_registry", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "windows-result 0.3.4", + "target": "windows_result" + }, + { + "id": "windows-strings 0.3.1", + "target": "windows_strings" + }, + { + "id": "windows-targets 0.53.5", + "target": "windows_targets" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.4.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "license-apache-2.0" + }, "windows-result 0.3.4": { "name": "windows-result", "version": "0.3.4", @@ -93253,6 +94991,54 @@ ], "license_file": "license-apache-2.0" }, + "windows-strings 0.3.1": { + "name": "windows-strings", + "version": "0.3.1", + "package_url": "https://github.com/microsoft/windows-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/windows-strings/0.3.1/download", + "sha256": "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" + } + }, + "targets": [ + { + "Library": { + "crate_name": "windows_strings", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "windows_strings", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "windows-link 0.1.3", + "target": "windows_link" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.3.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "license-apache-2.0" + }, "windows-strings 0.4.2": { "name": "windows-strings", "version": "0.4.2", @@ -93495,54 +95281,6 @@ ], "license_file": "license-apache-2.0" }, - "windows-sys 0.60.2": { - "name": "windows-sys", - "version": "0.60.2", - "package_url": "https://github.com/microsoft/windows-rs", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/windows-sys/0.60.2/download", - "sha256": "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" - } - }, - "targets": [ - { - "Library": { - "crate_name": "windows_sys", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "windows_sys", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "windows-targets 0.53.5", - "target": "windows_targets" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.60.2" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "license-apache-2.0" - }, "windows-sys 0.61.2": { "name": "windows-sys", "version": "0.61.2", @@ -97156,7 +98894,7 @@ ], "cfg(any(target_os = \"freebsd\", target_os = \"netbsd\"))": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" } ] @@ -98305,7 +100043,7 @@ "deps": { "common": [ { - "id": "libc 0.2.177", + "id": "libc 0.2.186", "target": "libc" }, { @@ -98607,6 +100345,9 @@ "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ "aarch64-apple-darwin" ], + "cfg(all(target_arch = \"wasm32\", any(target_os = \"unknown\", target_os = \"none\")))": [ + "wasm32-unknown-unknown" + ], "cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))": [ "wasm32-unknown-unknown" ], @@ -98712,6 +100453,12 @@ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu" ], + "cfg(any(unix, target_os = \"hermit\", target_os = \"wasi\"))": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu" + ], "cfg(any(unix, target_os = \"redox\"))": [ "aarch64-apple-darwin", "aarch64-unknown-linux-gnu", @@ -98732,6 +100479,18 @@ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu" ], + "cfg(not(all(target_arch = \"wasm32\", any(target_os = \"unknown\", target_os = \"none\"))))": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu" + ], + "cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu" + ], "cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))": [ "aarch64-apple-darwin", "aarch64-unknown-linux-gnu", @@ -98791,7 +100550,6 @@ ], "cfg(target_env = \"msvc\")": [], "cfg(target_env = \"sgx\")": [], - "cfg(target_feature = \"atomics\")": [], "cfg(target_os = \"android\")": [], "cfg(target_os = \"cloudabi\")": [], "cfg(target_os = \"dragonfly\")": [], @@ -98931,8 +100689,8 @@ "form_urlencoded 1.2.1", "fqdn 0.5.2", "fs_extra 1.3.0", - "futures 0.3.31", - "futures-util 0.3.31", + "futures 0.3.32", + "futures-util 0.3.32", "getifs 0.4.0", "getrandom 0.2.10", "goldenfile 1.8.0", @@ -99010,7 +100768,7 @@ "k256 0.13.4", "lazy_static 1.5.0", "leb128 0.2.5", - "libc 0.2.177", + "libc 0.2.186", "libcryptsetup-rs 0.15.0", "libflate 2.1.0", "libfuzzer-sys 0.4.7", @@ -99028,6 +100786,7 @@ "memchr 2.7.4", "memmap2 0.9.5", "metrics-proxy 0.1.0", + "meval 0.2.0", "minicbor 0.19.1", "minicbor-derive 0.13.0", "mockall 0.13.1", @@ -99076,7 +100835,7 @@ "publicsuffix 2.2.3", "qrcode 0.14.1", "quickcheck 1.0.3", - "quinn 0.11.5", + "quinn 0.11.9", "quinn-udp 0.5.5", "quote 1.0.42", "rand 0.8.5", @@ -99092,6 +100851,7 @@ "reqwest 0.12.24", "rexpect 0.6.2", "rgb 0.8.37", + "rig-core 0.36.0", "ring 0.17.14", "ripemd 0.1.3", "rlp 0.5.2", @@ -99167,7 +100927,7 @@ "tikv-jemalloc-ctl 0.6.0", "tikv-jemallocator 0.6.0", "time 0.3.47", - "tokio 1.48.0", + "tokio 1.52.1", "tokio-io-timeout 1.2.0", "tokio-metrics 0.4.0", "tokio-rusqlite 0.7.0", @@ -99182,7 +100942,7 @@ "tonic 0.12.3", "tonic-build 0.12.3", "tower 0.5.2", - "tower-http 0.6.6", + "tower-http 0.6.8", "tower-request-id 0.3.0", "tower-test 0.4.0", "tower_governor 0.7.0", @@ -99204,7 +100964,7 @@ "walkdir 2.5.0", "walrus 0.23.3", "warp 0.3.7", - "wasm-bindgen 0.2.100", + "wasm-bindgen 0.2.120", "wasm-encoder 0.245.1", "wasm-smith 0.245.1", "wasmparser 0.245.1", diff --git a/Cargo.Bazel.toml.lock b/Cargo.Bazel.toml.lock index 3287600e3c3b..e754719547e9 100644 --- a/Cargo.Bazel.toml.lock +++ b/Cargo.Bazel.toml.lock @@ -905,6 +905,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.7.9" @@ -2445,6 +2467,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "cobs" version = "0.3.0" @@ -2991,7 +3022,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.10.0", "crossterm_winapi", - "mio 1.0.2", + "mio 1.2.0", "parking_lot 0.12.5", "rustix 0.38.44", "signal-hook", @@ -3801,6 +3832,7 @@ dependencies = [ "memchr", "memmap2 0.9.5", "metrics-proxy", + "meval", "minicbor", "minicbor-derive", "mockall", @@ -3865,6 +3897,7 @@ dependencies = [ "reqwest 0.12.24", "rexpect", "rgb", + "rig-core", "ring 0.17.14", "ripemd", "rlp", @@ -3955,7 +3988,7 @@ dependencies = [ "tonic 0.12.3", "tonic-build", "tower 0.5.2", - "tower-http 0.6.6", + "tower-http 0.6.8", "tower-request-id", "tower-test", "tower_governor 0.7.0", @@ -4598,6 +4631,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom 7.1.3", + "pin-project-lite", +] + [[package]] name = "evm_rpc_client" version = "0.4.0" @@ -4962,9 +5006,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -4977,9 +5021,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -4987,15 +5031,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -5004,9 +5048,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -5025,9 +5069,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -5047,15 +5091,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -5065,9 +5109,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -5077,7 +5121,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -5141,8 +5184,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -5200,9 +5245,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "goldenfile" @@ -5924,9 +5969,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.5.9", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -6064,7 +6111,7 @@ dependencies = [ "serde_yaml_ng", "sha1", "sha2 0.10.9", - "socket2 0.6.1", + "socket2 0.6.3", "strum 0.27.2", "strum_macros 0.27.1", "systemstat", @@ -6116,7 +6163,7 @@ dependencies = [ "reqwest 0.12.24", "rustls 0.23.27", "serde", - "socket2 0.6.1", + "socket2 0.6.3", "strum 0.27.2", "thiserror 2.0.18", "tokio-util", @@ -6314,7 +6361,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-util", - "tower-http 0.6.6", + "tower-http 0.6.8", "tracing", "tracing-subscriber", ] @@ -6475,7 +6522,7 @@ dependencies = [ "tokio", "tokio-util", "tower 0.5.2", - "tower-http 0.6.6", + "tower-http 0.6.8", "tracing", "tracing-core", "tracing-serde 0.2.0", @@ -7422,10 +7469,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if 1.0.0", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -7731,9 +7780,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libcryptsetup-rs" @@ -8137,6 +8186,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lz4_flex" version = "0.11.5" @@ -8370,6 +8425,16 @@ dependencies = [ "url", ] +[[package]] +name = "meval" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9" +dependencies = [ + "fnv", + "nom 1.2.4", +] + [[package]] name = "mime" version = "0.3.17" @@ -8378,9 +8443,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -8445,15 +8510,14 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8576,6 +8640,15 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "neli" version = "0.6.4" @@ -8692,6 +8765,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" +[[package]] +name = "nom" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" + [[package]] name = "nom" version = "7.1.3" @@ -9265,7 +9344,7 @@ dependencies = [ "glob", "once_cell", "opentelemetry 0.21.0", - "ordered-float", + "ordered-float 4.2.0", "percent-encoding", "rand 0.8.5", "thiserror 1.0.68", @@ -9309,6 +9388,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -10637,37 +10725,44 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", "rustls 0.23.27", "socket2 0.5.9", - "thiserror 1.0.68", + "thiserror 2.0.18", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.7" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0a9b3a42929fad8a7c3de7f86ce0814cfa893328157672680e9fb1145549c5" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", - "rand 0.8.5", + "getrandom 0.3.1", + "lru-slab", + "rand 0.9.0", "ring 0.17.14", "rustc-hash 2.1.1", "rustls 0.23.27", + "rustls-pki-types", "slab", - "thiserror 1.0.68", + "thiserror 2.0.18", "tinyvec", "tracing", + "web-time", ] [[package]] @@ -11219,7 +11314,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-rustls 0.24.1", "tokio-util", @@ -11270,16 +11365,60 @@ dependencies = [ "tokio-rustls 0.26.0", "tokio-util", "tower 0.5.2", - "tower-http 0.6.6", + "tower-http 0.6.8", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.0", "web-sys", "webpki-roots 1.0.6", ] +[[package]] +name = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.4", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.27", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.0", + "tokio-util", + "tower 0.5.2", + "tower-http 0.6.8", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -11339,6 +11478,39 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rig-core" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac7d75266ac1f79b1faeff611c85cb40be00a0a983db10036591424f0433dc1" +dependencies = [ + "as-any", + "async-stream", + "base64 0.22.1", + "bytes", + "eventsource-stream", + "fastrand 2.3.0", + "futures", + "futures-timer", + "glob", + "http 1.3.1", + "mime", + "mime_guess", + "nanoid", + "ordered-float 5.3.0", + "pin-project-lite", + "reqwest 0.13.3", + "schemars 1.1.0", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.23.1", + "tracing", + "tracing-futures", + "url", +] + [[package]] name = "ring" version = "0.16.20" @@ -11693,6 +11865,7 @@ version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ + "aws-lc-rs", "brotli 8.0.1", "brotli-decompressor 5.0.0", "log", @@ -11779,6 +11952,7 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ + "web-time", "zeroize", ] @@ -11836,6 +12010,7 @@ version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ + "aws-lc-rs", "ring 0.17.14", "rustls-pki-types", "untrusted 0.9.0", @@ -11933,7 +12108,7 @@ checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "indexmap 2.13.0", - "schemars_derive", + "schemars_derive 0.8.21", "serde", "serde_json", ] @@ -11958,6 +12133,7 @@ checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "dyn-clone", "ref-cast", + "schemars_derive 1.1.0", "serde", "serde_json", ] @@ -11974,6 +12150,18 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "schemars_derive" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.110", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -12587,7 +12775,7 @@ checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio 0.8.10", - "mio 1.0.2", + "mio 1.2.0", "signal-hook", ] @@ -12896,12 +13084,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -13287,7 +13475,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -13300,6 +13499,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "systemd" version = "0.10.0" @@ -13775,17 +13984,17 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", - "mio 1.0.2", + "mio 1.2.0", "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.3", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -13803,9 +14012,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -13931,6 +14140,22 @@ dependencies = [ "tungstenite 0.21.0", ] +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.27", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tungstenite 0.23.0", + "webpki-roots 0.26.8", +] + [[package]] name = "tokio-tungstenite" version = "0.26.2" @@ -14046,7 +14271,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "socket2 0.6.1", + "socket2 0.6.3", "sync_wrapper 1.0.2", "tokio", "tokio-stream", @@ -14130,9 +14355,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", "bitflags 2.10.0", @@ -14280,6 +14505,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "futures", + "futures-task", + "pin-project", + "tracing", +] + [[package]] name = "tracing-journald" version = "0.3.2" @@ -14486,6 +14723,26 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.23.27", + "rustls-pki-types", + "sha1", + "thiserror 1.0.68", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.26.2" @@ -14816,7 +15073,7 @@ dependencies = [ "lalrpop 0.22.1", "lz4_flex", "nom-language", - "ordered-float", + "ordered-float 4.2.0", "regex", "serde", "serde_json", @@ -14982,47 +15239,32 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if 1.0.0", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.110", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "cfg-if 1.0.0", "js-sys", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -15030,22 +15272,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.110", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -15124,6 +15366,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.212.0" @@ -15432,9 +15687,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -15601,7 +15856,7 @@ dependencies = [ "windows-interface", "windows-link 0.1.3", "windows-result", - "windows-strings", + "windows-strings 0.4.2", ] [[package]] @@ -15659,6 +15914,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -15668,6 +15934,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -15713,15 +15988,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index ab83cc148cba..0d1b2c74b3ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "packages/icrc-ledger-types", "packages/pocket-ic", "packages/pocket-ic/test_canister", + "rs/ai_agent", "rs/artifact_pool", "rs/backup", "rs/bitcoin/adapter", diff --git a/bazel/rust.MODULE.bazel b/bazel/rust.MODULE.bazel index cd10f87f534a..4fbf909bf44a 100644 --- a/bazel/rust.MODULE.bazel +++ b/bazel/rust.MODULE.bazel @@ -939,6 +939,10 @@ crate.spec( package = "memchr", version = "2.7", ) +crate.spec( + package = "meval", + version = "^0.2.0", +) crate.spec( # When updating this, please make sure that the built # binary exports metrics http_cache_* after one @@ -1304,6 +1308,10 @@ crate.spec( package = "ring", version = "^0.17.7", ) +crate.spec( + package = "rig-core", + version = "^0.36.0", +) crate.spec( package = "ripemd", version = "^0.1.1", diff --git a/ic-os/components/guestos.bzl b/ic-os/components/guestos.bzl index 14b11e727f1b..25350338fb7a 100644 --- a/ic-os/components/guestos.bzl +++ b/ic-os/components/guestos.bzl @@ -69,6 +69,16 @@ def component_files(mode): Label("guestos/ollama/generate-ollama-tls-cert.service"): "/etc/systemd/system/generate-ollama-tls-cert.service", Label("guestos/ollama/ollama-tls.conf"): "/etc/stunnel/ollama-tls.conf", Label("guestos/ollama/ollama-tls.service"): "/etc/systemd/system/ollama-tls.service", + # IC AI agent orchestration HTTP API (started on AI nodes only, + # alongside ollama). Same TLS-via-stunnel pattern: the agent + # listens on 127.0.0.1:11501 and stunnel terminates TLS on + # :::11500. All three units are explicitly disabled in the + # GuestOS Dockerfile so non-AI nodes never run them. + Label("guestos/ai-agent/ic-ai-agent.service"): "/etc/systemd/system/ic-ai-agent.service", + Label("guestos/ai-agent/ic-ai-agent-tls.service"): "/etc/systemd/system/ic-ai-agent-tls.service", + Label("guestos/ai-agent/ic-ai-agent-tls.conf"): "/etc/stunnel/ic-ai-agent-tls.conf", + Label("guestos/ai-agent/generate-ic-ai-agent-tls-cert.sh"): "/opt/ic/bin/generate-ic-ai-agent-tls-cert.sh", + Label("guestos/ai-agent/generate-ic-ai-agent-tls-cert.service"): "/etc/systemd/system/generate-ic-ai-agent-tls-cert.service", Label("guestos/remote-attestation-server.service"): "/etc/systemd/system/remote-attestation-server.service", Label("guestos/generate-ic-config/generate-ic-config.service"): "/etc/systemd/system/generate-ic-config.service", Label("guestos/share/ic-boundary.env"): "/opt/ic/share/ic-boundary.env", diff --git a/ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.service b/ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.service new file mode 100644 index 000000000000..ff7c77a2284c --- /dev/null +++ b/ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.service @@ -0,0 +1,18 @@ +[Unit] +Description=Generate self-signed TLS cert for the ic-ai-agent stunnel proxy +Documentation=man:openssl-req(1) + +After=var.mount local-fs.target +RequiresMountsFor=/var/lib + +ConditionPathExists=!/var/lib/ic-ai-agent-tls/stunnel.pem + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/opt/ic/bin/generate-ic-ai-agent-tls-cert.sh + +[Install] +# Disabled by default in the GuestOS Dockerfile. Started on demand by +# manage-ai-agent.sh, itself driven by the orchestrator's AiNodeManager. +WantedBy=multi-user.target diff --git a/ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.sh b/ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.sh new file mode 100755 index 000000000000..61618713c0fb --- /dev/null +++ b/ic-os/components/guestos/ai-agent/generate-ic-ai-agent-tls-cert.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# Generate a self-signed TLS certificate used by stunnel to terminate TLS in +# front of the local ic-ai-agent backend. +# +# Mirrors generate-ollama-tls-cert.sh in shape; idempotent oneshot. + +set -eu + +CERT_DIR=/var/lib/ic-ai-agent-tls +CERT_FILE="${CERT_DIR}/cert.pem" +KEY_FILE="${CERT_DIR}/key.pem" +COMBINED_FILE="${CERT_DIR}/stunnel.pem" + +mkdir -p "${CERT_DIR}" +chmod 0750 "${CERT_DIR}" + +if [ -s "${CERT_FILE}" ] && [ -s "${KEY_FILE}" ] && [ -s "${COMBINED_FILE}" ]; then + echo "TLS material already present at ${CERT_DIR}, nothing to do." >&2 + exit 0 +fi + +# Stable Subject CN derived from the machine-id. +CN="ic-ai-agent" +if [ -s /etc/machine-id ]; then + CN="ic-ai-agent-$(cat /etc/machine-id)" +fi + +umask 077 + +if ! openssl req \ + -x509 \ + -newkey rsa:2048 \ + -keyout "${KEY_FILE}" \ + -out "${CERT_FILE}" \ + -days 3650 \ + -nodes \ + -subj "/CN=${CN}" \ + -addext "subjectAltName=DNS:${CN},DNS:localhost,IP:127.0.0.1,IP:0.0.0.0" \ + 2>/tmp/openssl-stderr.$$; then + echo "openssl req failed:" >&2 + cat /tmp/openssl-stderr.$$ >&2 || true + rm -f /tmp/openssl-stderr.$$ + exit 1 +fi +rm -f /tmp/openssl-stderr.$$ + +cat "${CERT_FILE}" "${KEY_FILE}" >"${COMBINED_FILE}" + +TARGET_GROUP="root" +if getent group stunnel4 >/dev/null 2>&1; then + TARGET_GROUP="stunnel4" +fi +chown "root:${TARGET_GROUP}" "${KEY_FILE}" "${CERT_FILE}" "${COMBINED_FILE}" +chmod 0640 "${KEY_FILE}" "${CERT_FILE}" "${COMBINED_FILE}" + +echo "Generated self-signed TLS cert at ${CERT_FILE} for CN=${CN} (group=${TARGET_GROUP})." >&2 diff --git a/ic-os/components/guestos/ai-agent/ic-ai-agent-tls.conf b/ic-os/components/guestos/ai-agent/ic-ai-agent-tls.conf new file mode 100644 index 000000000000..fa136d9f7ea4 --- /dev/null +++ b/ic-os/components/guestos/ai-agent/ic-ai-agent-tls.conf @@ -0,0 +1,24 @@ +# stunnel configuration for the IC AI agent TLS reverse proxy. +# +# Terminates TLS on :::11500 with a self-signed certificate generated at +# first boot by generate-ic-ai-agent-tls-cert.service, and forwards +# plaintext traffic to the local agent backend on 127.0.0.1:11501. + +# Run in foreground so systemd supervises the main PID directly. +foreground = yes +pid = + +# Drop privileges after binding to the listener. +setuid = stunnel4 +setgid = stunnel4 + +# Combined PEM (cert + key). +cert = /var/lib/ic-ai-agent-tls/stunnel.pem + +# We don't authenticate clients (untrusted self-signed cert anyway). +verify = 0 + +[ic-ai-agent] +# IPv6 wildcard with dual-stack enabled accepts both v4 and v6. +accept = :::11500 +connect = 127.0.0.1:11501 diff --git a/ic-os/components/guestos/ai-agent/ic-ai-agent-tls.service b/ic-os/components/guestos/ai-agent/ic-ai-agent-tls.service new file mode 100644 index 000000000000..025b39067348 --- /dev/null +++ b/ic-os/components/guestos/ai-agent/ic-ai-agent-tls.service @@ -0,0 +1,27 @@ +[Unit] +Description=stunnel TLS terminator in front of the IC AI agent +Documentation=man:stunnel(8) + +# Mirrors the design of ollama-tls.service: stunnel runs independently of +# ic-ai-agent.service and just listens on 11500/tcp, forwarding plaintext +# to 127.0.0.1:11501. If the agent isn't running, clients get a refused +# upstream connection but the TLS listener stays up. +# +# Do NOT use BindsTo=ic-ai-agent.service; the agent service is explicitly +# disabled in the GuestOS Dockerfile and BindsTo would cause systemd to +# Stop this unit at boot. +After=network-online.target generate-ic-ai-agent-tls-cert.service +Wants=network-online.target generate-ic-ai-agent-tls-cert.service + +# The cert is mandatory; if the generator failed, refuse to start. +ConditionPathExists=/var/lib/ic-ai-agent-tls/stunnel.pem + +[Service] +Type=simple +ExecStart=/usr/bin/stunnel /etc/stunnel/ic-ai-agent-tls.conf + +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/ic-os/components/guestos/ai-agent/ic-ai-agent.service b/ic-os/components/guestos/ai-agent/ic-ai-agent.service new file mode 100644 index 000000000000..4232ea70a609 --- /dev/null +++ b/ic-os/components/guestos/ai-agent/ic-ai-agent.service @@ -0,0 +1,33 @@ +[Unit] +Description=IC AI agent orchestration HTTP API (disabled by default) +Documentation=https://github.com/anomalyco/opencode + +# Disabled by default in the GuestOS Dockerfile. Started on demand by +# manage-ai-agent.sh (driven by the orchestrator's AiNodeManager) together +# with the TLS cert generator and the stunnel proxy. Regular non-AI nodes +# never bring this up. + +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +DynamicUser=yes + +# Bind to loopback only on a non-privileged port. External clients reach +# the service through the stunnel TLS terminator on :::11500 (see +# /etc/systemd/system/ic-ai-agent-tls.service), which forwards plaintext to +# 127.0.0.1:11501. +Environment=IC_AI_AGENT_ADDR=127.0.0.1:11501 + +ExecStart=/opt/ic/bin/ic-ai-agent --addr 127.0.0.1:11501 + +Restart=on-failure +RestartSec=5s + +# The agent is a stateless HTTP server: no need for state directories. +# DynamicUser provides automatic /tmp isolation; no extra hardening needed +# beyond the dm-verity-protected rootfs. + +[Install] +WantedBy=multi-user.target diff --git a/ic-os/components/guestos/ollama/manage-ollama.sh b/ic-os/components/guestos/ollama/manage-ollama.sh index ff3288c029ac..11e67b184aa7 100755 --- a/ic-os/components/guestos/ollama/manage-ollama.sh +++ b/ic-os/components/guestos/ollama/manage-ollama.sh @@ -18,16 +18,28 @@ ACTION="$1" # of this up. case "$ACTION" in start) - # Cert must exist before stunnel starts; service is `Type=oneshot` - # with `RemainAfterExit=yes`, so `start` is idempotent. + # Cert must exist before stunnel starts; cert services are + # `Type=oneshot` with `RemainAfterExit=yes`, so `start` is + # idempotent. /bin/systemctl start generate-ollama-tls-cert.service /bin/systemctl start ollama-tls.service /bin/systemctl start ollama.service + + # The IC AI agent service runs alongside ollama on AI nodes, + # exposing an HTTP orchestration API on a separate TLS port + # (11500). It's started with the same lifecycle as ollama: any + # node that flips to AI mode brings both up, any node that flips + # away brings both down. + /bin/systemctl start generate-ic-ai-agent-tls-cert.service + /bin/systemctl start ic-ai-agent-tls.service + /bin/systemctl start ic-ai-agent.service ;; stop) - # Stop in reverse order. The cert generator is `RemainAfterExit=yes` - # and has nothing to tear down; leave it active so the cert remains + # Stop in reverse order. Cert generators are `RemainAfterExit=yes` + # and have nothing to tear down; leave them active so certs remain # valid for the next start. + /bin/systemctl stop ic-ai-agent.service + /bin/systemctl stop ic-ai-agent-tls.service /bin/systemctl stop ollama.service /bin/systemctl stop ollama-tls.service ;; diff --git a/ic-os/guestos/context/Dockerfile b/ic-os/guestos/context/Dockerfile index f871ddccbc73..c261fb6685cf 100644 --- a/ic-os/guestos/context/Dockerfile +++ b/ic-os/guestos/context/Dockerfile @@ -116,7 +116,10 @@ RUN systemctl disable \ fstrim.timer \ ollama.service \ ollama-tls.service \ - generate-ollama-tls-cert.service + generate-ollama-tls-cert.service \ + ic-ai-agent.service \ + ic-ai-agent-tls.service \ + generate-ic-ai-agent-tls-cert.service # ------ GUESTOS WORK -------------------------------------------- diff --git a/ic-os/guestos/defs.bzl b/ic-os/guestos/defs.bzl index d512a313aafe..a1c8b82f5edc 100644 --- a/ic-os/guestos/defs.bzl +++ b/ic-os/guestos/defs.bzl @@ -55,6 +55,7 @@ def image_deps(mode, malicious = False): "//rs/ic_os/release:custom_metrics": "/opt/ic/bin/custom_metrics:0755", # Collects and reports custom metrics. "//rs/ic_os/remote_attestation/server": "/opt/ic/bin/remote_attestation_server:0755", # Remote Attestation service "//rs/ic_os/guest_upgrade/client": "/opt/ic/bin/guest_upgrade_client:0755", # Disk encryption key exchange client + "//rs/ai_agent:ic-ai-agent": "/opt/ic/bin/ic-ai-agent:0755", # AI agent orchestration HTTP API (started on AI nodes) # additional libraries to install "//rs/ic_os/release:nss_icos": "/usr/lib/x86_64-linux-gnu/libnss_icos.so.2:0644", # Allows referring to the guest IPv6 by name guestos from host, and host as hostos from guest. diff --git a/publish/binaries/BUILD.bazel b/publish/binaries/BUILD.bazel index 322d621df5d8..b3a721544dec 100644 --- a/publish/binaries/BUILD.bazel +++ b/publish/binaries/BUILD.bazel @@ -9,6 +9,7 @@ ALL_BINARIES = { # Keep sorted "canister_sandbox": "//rs/canister_sandbox", "compiler_sandbox": "//rs/canister_sandbox:compiler_sandbox", + "ic-ai-agent": "//rs/ai_agent:ic-ai-agent", "ic-btc-adapter": "//rs/bitcoin/adapter:ic-btc-adapter", "replica": "//rs/replica:replica", "rate-limiting-canister-client": "//rs/boundary_node/rate_limits/canister_client:rate-limiting-canister-client", diff --git a/rs/ai_agent/BUILD.bazel b/rs/ai_agent/BUILD.bazel new file mode 100644 index 000000000000..7fdcc94e2da5 --- /dev/null +++ b/rs/ai_agent/BUILD.bazel @@ -0,0 +1,44 @@ +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +DEPENDENCIES = [ + # Keep sorted. + "@crate_index//:anyhow", + "@crate_index//:axum", + "@crate_index//:chrono", + "@crate_index//:meval", + "@crate_index//:rig_core", + "@crate_index//:serde", + "@crate_index//:serde_json", + "@crate_index//:slog", + "@crate_index//:slog-async", + "@crate_index//:slog-term", + "@crate_index//:thiserror", + "@crate_index//:tokio", + "@crate_index//:tower", + "@crate_index//:tower-http", +] + +rust_library( + name = "ai_agent", + srcs = glob( + ["src/**/*.rs"], + exclude = ["src/main.rs"], + ), + crate_name = "ic_ai_agent", + deps = DEPENDENCIES, +) + +rust_binary( + name = "ic-ai-agent", + srcs = ["src/main.rs"], + visibility = [ + "//ic-os/guestos:__subpackages__", + "//rs:release-pkg", + ], + deps = DEPENDENCIES + [ + ":ai_agent", + "@crate_index//:clap", + ], +) diff --git a/rs/ai_agent/Cargo.toml b/rs/ai_agent/Cargo.toml new file mode 100644 index 000000000000..766572f036f5 --- /dev/null +++ b/rs/ai_agent/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ic-ai-agent" +version.workspace = true +authors.workspace = true +edition.workspace = true +description.workspace = true +documentation.workspace = true + +[[bin]] +name = "ic-ai-agent" +path = "src/main.rs" + +[dependencies] +anyhow = { workspace = true } +axum = { workspace = true } +clap = { workspace = true } +meval = "0.2" +rig-core = "0.36" +serde = { workspace = true } +serde_json = { workspace = true } +slog = { workspace = true } +slog-async = { workspace = true } +slog-term = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tower = { workspace = true } +tower-http = { workspace = true } +chrono = { workspace = true } diff --git a/rs/ai_agent/src/config.rs b/rs/ai_agent/src/config.rs new file mode 100644 index 000000000000..90038f072fb7 --- /dev/null +++ b/rs/ai_agent/src/config.rs @@ -0,0 +1,62 @@ +//! Runtime configuration for the AI agent service. +//! +//! In v1 the only required configuration is the Gemini API key, which is +//! supplied at runtime via `POST /v1/config` rather than via env vars (see +//! the spec). Defaults below cover the rest. + +use serde::{Deserialize, Serialize}; + +/// Default Gemini model used if `/v1/config` doesn't override it. +pub const DEFAULT_GEMINI_MODEL: &str = "gemini-2.0-flash"; + +/// Default agent system prompt. +pub const DEFAULT_PREAMBLE: &str = "You are a concise, helpful assistant. \ + When tools are provided, prefer using them for any factual, computational, \ + or time-sensitive request."; + +/// Default cap on tool-call turns per agent invocation. +pub const DEFAULT_MAX_TURNS: usize = 5; + +/// Static configuration baked in at startup. The actual provider client is +/// created later, when `/v1/config` is invoked with the API key. +#[derive(Clone, Debug)] +pub struct AppConfig { + pub default_model: String, + pub default_preamble: String, + pub default_max_turns: usize, +} + +impl Default for AppConfig { + fn default() -> Self { + Self { + default_model: DEFAULT_GEMINI_MODEL.to_string(), + default_preamble: DEFAULT_PREAMBLE.to_string(), + default_max_turns: DEFAULT_MAX_TURNS, + } + } +} + +/// Body of `POST /v1/config`. Currently only Gemini is supported. +#[derive(Debug, Deserialize)] +pub struct ConfigRequest { + /// Provider name. Defaults to `gemini`. + #[serde(default = "default_provider")] + pub provider: String, + /// Provider API key. Required for `gemini`. + pub api_key: String, + /// Optional model override. + pub model: Option, + /// Optional default preamble override. + pub preamble: Option, +} + +fn default_provider() -> String { + "gemini".to_string() +} + +#[derive(Debug, Serialize)] +pub struct ConfigResponse { + pub status: &'static str, + pub provider: String, + pub model: String, +} diff --git a/rs/ai_agent/src/handlers/chat.rs b/rs/ai_agent/src/handlers/chat.rs new file mode 100644 index 000000000000..6b90fafa7091 --- /dev/null +++ b/rs/ai_agent/src/handlers/chat.rs @@ -0,0 +1,119 @@ +use std::sync::Arc; + +use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; +use rig::completion::{Prompt, message::Message}; +use slog::warn; + +use crate::{ + models::{ + ChatMessage, ChatRequest, ChatResponse, ErrorBody, request::ChatRole, + response::SerializableChatMessage, + }, + providers::provider_not_configured, + state::AppState, + tools::validate_tool_names, +}; + +/// `POST /v1/agent/chat` — multi-turn agent invocation, with caller-managed +/// history. The server appends the new exchange to `history` and returns the +/// updated transcript. +pub async fn chat( + State(state): State>, + Json(req): Json, +) -> impl IntoResponse { + if req.prompt.trim().is_empty() { + return ( + StatusCode::BAD_REQUEST, + Json(ErrorBody::new("prompt must not be empty")), + ) + .into_response(); + } + if let Err(unknown) = validate_tool_names(&req.tools) { + return ( + StatusCode::BAD_REQUEST, + Json(ErrorBody::new(format!("unknown tool: {unknown}"))), + ) + .into_response(); + } + + let provider_guard = state.provider.read().await; + let provider = match provider_guard.as_ref() { + Some(p) => p.clone(), + None => { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ErrorBody::new(provider_not_configured().to_string())), + ) + .into_response(); + } + }; + drop(provider_guard); + + let preamble = req + .preamble + .as_deref() + .unwrap_or(state.config.default_preamble.as_str()); + let max_turns = req.max_turns.unwrap_or(state.config.default_max_turns); + + let agent = match provider.build_agent(preamble, &[]) { + Ok(a) => a, + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorBody::new(format!("agent build failed: {e}"))), + ) + .into_response(); + } + }; + + let history_msgs: Vec = req.history.iter().map(to_rig_message).collect(); + + let result = agent + .prompt(req.prompt.as_str()) + .with_history(history_msgs) + .max_turns(max_turns) + .extended_details() + .await; + + match result { + Ok(resp) => { + let turns_used = resp.messages.as_ref().map(Vec::len).unwrap_or(1); + let mut full_history: Vec = req + .history + .iter() + .map(SerializableChatMessage::from) + .collect(); + full_history.push(SerializableChatMessage::from(&ChatMessage { + role: ChatRole::User, + content: req.prompt.clone(), + })); + full_history.push(SerializableChatMessage::from(&ChatMessage { + role: ChatRole::Assistant, + content: resp.output.clone(), + })); + let body = ChatResponse { + response: resp.output, + history: full_history, + turns_used, + provider: provider.name().to_string(), + model: provider.model().to_string(), + }; + (StatusCode::OK, Json(body)).into_response() + } + Err(e) => { + warn!(state.log, "agent chat failed"; "error" => %e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorBody::new(format!("Agent failed: {e}"))), + ) + .into_response() + } + } +} + +fn to_rig_message(m: &ChatMessage) -> Message { + match m.role { + ChatRole::User => Message::user(m.content.clone()), + ChatRole::Assistant => Message::assistant(m.content.clone()), + } +} diff --git a/rs/ai_agent/src/handlers/config.rs b/rs/ai_agent/src/handlers/config.rs new file mode 100644 index 000000000000..03f7c1668c4c --- /dev/null +++ b/rs/ai_agent/src/handlers/config.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; +use slog::{info, warn}; + +use crate::{ + config::{ConfigRequest, ConfigResponse}, + models::ErrorBody, + providers::AiProvider, + state::AppState, +}; + +/// `POST /v1/config` — install/replace the active provider client. +/// +/// Until this endpoint is called successfully, `/v1/agent/run` and +/// `/v1/agent/chat` return 503. +pub async fn configure( + State(state): State>, + Json(req): Json, +) -> impl IntoResponse { + match AiProvider::from_request(&req, &state.config.default_model) { + Ok(provider) => { + let resp = ConfigResponse { + status: "ok", + provider: provider.name().to_string(), + model: provider.model().to_string(), + }; + *state.provider.write().await = Some(provider); + info!( + state.log, + "provider configured"; + "provider" => &resp.provider, + "model" => &resp.model + ); + (StatusCode::OK, Json(resp)).into_response() + } + Err(e) => { + warn!(state.log, "provider config rejected"; "error" => %e); + ( + StatusCode::BAD_REQUEST, + Json(ErrorBody::new(format!("invalid config: {e}"))), + ) + .into_response() + } + } +} diff --git a/rs/ai_agent/src/handlers/health.rs b/rs/ai_agent/src/handlers/health.rs new file mode 100644 index 000000000000..e2d43f749d1d --- /dev/null +++ b/rs/ai_agent/src/handlers/health.rs @@ -0,0 +1,19 @@ +use std::sync::Arc; + +use axum::{Json, extract::State}; + +use crate::{models::HealthResponse, state::AppState}; + +/// `GET /v1/health` — liveness probe; reports active provider/model if any. +pub async fn health(State(state): State>) -> Json { + let provider = state.provider.read().await; + let (provider_name, model) = match provider.as_ref() { + Some(p) => (Some(p.name().to_string()), Some(p.model().to_string())), + None => (None, None), + }; + Json(HealthResponse { + status: "ok", + provider: provider_name, + model, + }) +} diff --git a/rs/ai_agent/src/handlers/mod.rs b/rs/ai_agent/src/handlers/mod.rs new file mode 100644 index 000000000000..ed7870407bc0 --- /dev/null +++ b/rs/ai_agent/src/handlers/mod.rs @@ -0,0 +1,4 @@ +pub mod chat; +pub mod config; +pub mod health; +pub mod run; diff --git a/rs/ai_agent/src/handlers/run.rs b/rs/ai_agent/src/handlers/run.rs new file mode 100644 index 000000000000..cc6c398eafd4 --- /dev/null +++ b/rs/ai_agent/src/handlers/run.rs @@ -0,0 +1,87 @@ +use std::sync::Arc; + +use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; +use rig::completion::Prompt; +use slog::warn; + +use crate::{ + models::{ErrorBody, RunRequest, RunResponse}, + providers::provider_not_configured, + state::AppState, + tools::validate_tool_names, +}; + +/// `POST /v1/agent/run` — single-turn agent invocation. +pub async fn run( + State(state): State>, + Json(req): Json, +) -> impl IntoResponse { + if req.prompt.trim().is_empty() { + return ( + StatusCode::BAD_REQUEST, + Json(ErrorBody::new("prompt must not be empty")), + ) + .into_response(); + } + if let Err(unknown) = validate_tool_names(&req.tools) { + return ( + StatusCode::BAD_REQUEST, + Json(ErrorBody::new(format!("unknown tool: {unknown}"))), + ) + .into_response(); + } + + let provider_guard = state.provider.read().await; + let provider = match provider_guard.as_ref() { + Some(p) => p.clone(), + None => { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ErrorBody::new(provider_not_configured().to_string())), + ) + .into_response(); + } + }; + drop(provider_guard); + + let preamble = req + .preamble + .as_deref() + .unwrap_or(state.config.default_preamble.as_str()); + let max_turns = req.max_turns.unwrap_or(state.config.default_max_turns); + + let agent = match provider.build_agent(preamble, &req.context) { + Ok(a) => a, + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorBody::new(format!("agent build failed: {e}"))), + ) + .into_response(); + } + }; + + let result = agent + .prompt(req.prompt.as_str()) + .max_turns(max_turns) + .extended_details() + .await; + + match result { + Ok(resp) => { + let turns_used = resp.messages.as_ref().map(Vec::len).unwrap_or(1); + let body = RunResponse { + response: resp.output, + turns_used, + provider: provider.name().to_string(), + model: provider.model().to_string(), + }; + (StatusCode::OK, Json(body)).into_response() + } + Err(e) => { + warn!(state.log, "agent run failed"; "error" => %e); + let msg = format!("Agent failed: {e}"); + (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorBody::new(msg))).into_response() + } + } +} diff --git a/rs/ai_agent/src/lib.rs b/rs/ai_agent/src/lib.rs new file mode 100644 index 000000000000..ef98e6c4e33f --- /dev/null +++ b/rs/ai_agent/src/lib.rs @@ -0,0 +1,15 @@ +//! IC AI agent orchestration library. +//! +//! Lightweight HTTP API exposing single-turn (`/v1/agent/run`) and +//! multi-turn (`/v1/agent/chat`) agent endpoints, backed by the +//! [`rig`](https://docs.rig.rs/) library. Gemini is the default and currently +//! only supported provider; new providers are added by extending +//! [`providers::AiProvider`]. + +pub mod config; +pub mod handlers; +pub mod models; +pub mod providers; +pub mod router; +pub mod state; +pub mod tools; diff --git a/rs/ai_agent/src/main.rs b/rs/ai_agent/src/main.rs new file mode 100644 index 000000000000..43e795546da9 --- /dev/null +++ b/rs/ai_agent/src/main.rs @@ -0,0 +1,41 @@ +//! IC AI agent service binary entry point. +//! +//! Starts an Axum server exposing the agent orchestration HTTP API +//! described in `ai-agents-in-nodes-spec.md`. The server is plaintext; +//! a stunnel sidecar terminates TLS for external traffic. + +use clap::Parser; +use ic_ai_agent::{config::AppConfig, router::build_router, state::AppState}; +use slog::{Drain, Logger, info, o}; +use std::{net::SocketAddr, sync::Arc}; + +#[derive(Debug, Parser)] +#[command(name = "ic-ai-agent", about = "IC AI agent orchestration HTTP API")] +struct Cli { + /// Address to bind the HTTP server to. + #[arg(long, env = "IC_AI_AGENT_ADDR", default_value = "127.0.0.1:11501")] + addr: SocketAddr, +} + +fn make_logger() -> Logger { + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::FullFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + Logger::root(drain, o!("component" => "ic-ai-agent")) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + let log = make_logger(); + + let config = AppConfig::default(); + let state = Arc::new(AppState::new(config, log.clone())); + let app = build_router(state); + + info!(log, "ic-ai-agent listening"; "addr" => %cli.addr); + + let listener = tokio::net::TcpListener::bind(cli.addr).await?; + axum::serve(listener, app.into_make_service()).await?; + Ok(()) +} diff --git a/rs/ai_agent/src/models/mod.rs b/rs/ai_agent/src/models/mod.rs new file mode 100644 index 000000000000..f6630fd290be --- /dev/null +++ b/rs/ai_agent/src/models/mod.rs @@ -0,0 +1,7 @@ +//! Wire types for HTTP request/response bodies. + +pub mod request; +pub mod response; + +pub use request::{ChatMessage, ChatRequest, RunRequest}; +pub use response::{ChatResponse, ErrorBody, HealthResponse, RunResponse}; diff --git a/rs/ai_agent/src/models/request.rs b/rs/ai_agent/src/models/request.rs new file mode 100644 index 000000000000..5dabebd788f6 --- /dev/null +++ b/rs/ai_agent/src/models/request.rs @@ -0,0 +1,39 @@ +use serde::Deserialize; + +/// Body of `POST /v1/agent/run`. +#[derive(Debug, Deserialize)] +pub struct RunRequest { + pub prompt: String, + pub preamble: Option, + #[serde(default)] + pub context: Vec, + #[serde(default)] + pub tools: Vec, + pub max_turns: Option, +} + +/// Single message in a chat history. +#[derive(Debug, Clone, Deserialize)] +pub struct ChatMessage { + pub role: ChatRole, + pub content: String, +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, serde::Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ChatRole { + User, + Assistant, +} + +/// Body of `POST /v1/agent/chat`. +#[derive(Debug, Deserialize)] +pub struct ChatRequest { + pub prompt: String, + #[serde(default)] + pub history: Vec, + pub preamble: Option, + #[serde(default)] + pub tools: Vec, + pub max_turns: Option, +} diff --git a/rs/ai_agent/src/models/response.rs b/rs/ai_agent/src/models/response.rs new file mode 100644 index 000000000000..c5831e5ef885 --- /dev/null +++ b/rs/ai_agent/src/models/response.rs @@ -0,0 +1,56 @@ +use serde::Serialize; + +use super::request::ChatMessage; + +#[derive(Debug, Serialize)] +pub struct HealthResponse { + pub status: &'static str, + pub provider: Option, + pub model: Option, +} + +#[derive(Debug, Serialize)] +pub struct RunResponse { + pub response: String, + pub turns_used: usize, + pub provider: String, + pub model: String, +} + +#[derive(Debug, Serialize)] +pub struct ChatResponse { + pub response: String, + pub history: Vec, + pub turns_used: usize, + pub provider: String, + pub model: String, +} + +#[derive(Debug, Serialize)] +pub struct SerializableChatMessage { + pub role: String, + pub content: String, +} + +impl From<&ChatMessage> for SerializableChatMessage { + fn from(m: &ChatMessage) -> Self { + Self { + role: match m.role { + super::request::ChatRole::User => "user".to_string(), + super::request::ChatRole::Assistant => "assistant".to_string(), + }, + content: m.content.clone(), + } + } +} + +#[derive(Debug, Serialize)] +pub struct ErrorBody { + pub error: String, +} + +impl ErrorBody { + pub fn new(msg: impl Into) -> Self { + Self { error: msg.into() } + } +} diff --git a/rs/ai_agent/src/providers/mod.rs b/rs/ai_agent/src/providers/mod.rs new file mode 100644 index 000000000000..f3d1967e9352 --- /dev/null +++ b/rs/ai_agent/src/providers/mod.rs @@ -0,0 +1,83 @@ +//! Provider abstraction. +//! +//! Wraps the underlying `rig` provider client behind an enum so new +//! providers can be added without touching handler code: extend +//! [`AiProvider`] with a new variant, add an arm in [`AiProvider::from_request`] +//! and in [`AiProvider::build_agent`], done. + +use anyhow::anyhow; +use rig::{ + agent::Agent, + client::CompletionClient, + providers::gemini::{Client as GeminiClient, completion::CompletionModel as GeminiCompletion}, +}; + +use crate::{ + config::ConfigRequest, + tools::{Calculator, CurrentDateTime}, +}; + +/// Active AI provider client. Currently Gemini-only; new providers slot in +/// here as additional variants. +#[derive(Clone)] +pub enum AiProvider { + Gemini { client: GeminiClient, model: String }, +} + +impl AiProvider { + /// Build a provider client from a `POST /v1/config` body. + pub fn from_request(req: &ConfigRequest, default_model: &str) -> anyhow::Result { + match req.provider.as_str() { + "gemini" => { + if req.api_key.trim().is_empty() { + return Err(anyhow!("api_key must not be empty")); + } + let client = GeminiClient::new(&req.api_key) + .map_err(|e| anyhow!("failed to construct Gemini client: {e}"))?; + let model = req + .model + .clone() + .unwrap_or_else(|| default_model.to_string()); + Ok(Self::Gemini { client, model }) + } + other => Err(anyhow!("unknown provider: {other}")), + } + } + + pub fn name(&self) -> &'static str { + match self { + AiProvider::Gemini { .. } => "gemini", + } + } + + pub fn model(&self) -> &str { + match self { + AiProvider::Gemini { model, .. } => model.as_str(), + } + } + + /// Build a configured agent. Tools and context can be filtered/added by + /// the caller, but in v1 we always wire all built-in tools so requests + /// can reference any of them by name. + pub fn build_agent( + &self, + preamble: &str, + contexts: &[String], + ) -> anyhow::Result> { + match self { + AiProvider::Gemini { client, model } => { + let mut builder = client.agent(model).preamble(preamble); + for ctx in contexts { + builder = builder.context(ctx); + } + let agent = builder.tool(Calculator).tool(CurrentDateTime).build(); + Ok(agent) + } + } + } +} + +/// Shared error message helper for missing-provider conditions. +pub fn provider_not_configured() -> anyhow::Error { + anyhow!("provider not configured; POST /v1/config first") +} diff --git a/rs/ai_agent/src/router.rs b/rs/ai_agent/src/router.rs new file mode 100644 index 000000000000..e6e8dd787dcf --- /dev/null +++ b/rs/ai_agent/src/router.rs @@ -0,0 +1,24 @@ +//! Axum router wiring. + +use std::sync::Arc; + +use axum::{ + Router, + routing::{get, post}, +}; +use tower_http::trace::TraceLayer; + +use crate::{ + handlers::{chat::chat, config::configure, health::health, run::run}, + state::AppState, +}; + +pub fn build_router(state: Arc) -> Router { + Router::new() + .route("/v1/health", get(health)) + .route("/v1/config", post(configure)) + .route("/v1/agent/run", post(run)) + .route("/v1/agent/chat", post(chat)) + .layer(TraceLayer::new_for_http()) + .with_state(state) +} diff --git a/rs/ai_agent/src/state.rs b/rs/ai_agent/src/state.rs new file mode 100644 index 000000000000..0a95d17a6adf --- /dev/null +++ b/rs/ai_agent/src/state.rs @@ -0,0 +1,27 @@ +//! Per-process shared state. +//! +//! Holds the active provider client (set by `POST /v1/config`) plus static +//! defaults. Wrapped in `Arc>` so `/v1/config` can update it +//! at runtime without recreating the router. + +use crate::{config::AppConfig, providers::AiProvider}; +use slog::Logger; +use tokio::sync::RwLock; + +/// Mutable runtime state shared across handlers. +pub struct AppState { + pub config: AppConfig, + pub log: Logger, + /// `None` until `POST /v1/config` populates it. + pub provider: RwLock>, +} + +impl AppState { + pub fn new(config: AppConfig, log: Logger) -> Self { + Self { + config, + log, + provider: RwLock::new(None), + } + } +} diff --git a/rs/ai_agent/src/tools/calculator.rs b/rs/ai_agent/src/tools/calculator.rs new file mode 100644 index 000000000000..6b0087f6cf75 --- /dev/null +++ b/rs/ai_agent/src/tools/calculator.rs @@ -0,0 +1,58 @@ +//! Tool: evaluate a basic arithmetic expression with `meval`. + +use rig::{completion::ToolDefinition, tool::Tool}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +#[derive(Debug, Deserialize)] +pub struct CalculatorArgs { + pub expression: String, +} + +#[derive(Debug, Serialize)] +pub struct CalculatorOutput { + pub expression: String, + pub result: f64, +} + +#[derive(Debug, thiserror::Error)] +pub enum CalculatorError { + #[error("failed to evaluate expression: {0}")] + Eval(#[from] meval::Error), +} + +pub struct Calculator; + +impl Tool for Calculator { + const NAME: &'static str = "calculator"; + type Error = CalculatorError; + type Args = CalculatorArgs; + type Output = CalculatorOutput; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: Self::NAME.to_string(), + description: "Evaluates a basic arithmetic expression and returns the numeric result. \ + Supports +, -, *, /, ^, parentheses, and standard math functions like sin, cos, sqrt." + .to_string(), + parameters: json!({ + "type": "object", + "properties": { + "expression": { + "type": "string", + "description": "An arithmetic expression, e.g. '(144 / 12) * 7'" + } + }, + "required": ["expression"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + let result = meval::eval_str(&args.expression)?; + Ok(CalculatorOutput { + expression: args.expression, + result, + }) + } +} diff --git a/rs/ai_agent/src/tools/current_datetime.rs b/rs/ai_agent/src/tools/current_datetime.rs new file mode 100644 index 000000000000..ac64b7daa93e --- /dev/null +++ b/rs/ai_agent/src/tools/current_datetime.rs @@ -0,0 +1,51 @@ +//! Tool: returns the current UTC date/time. Useful for time-sensitive +//! prompts. + +use chrono::Utc; +use rig::{completion::ToolDefinition, tool::Tool}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +#[derive(Debug, Deserialize)] +pub struct CurrentDateTimeArgs {} + +#[derive(Debug, Serialize)] +pub struct CurrentDateTimeOutput { + pub utc: String, + pub unix_seconds: i64, +} + +#[derive(Debug, thiserror::Error)] +#[error("current_datetime tool: {0}")] +pub struct CurrentDateTimeError(String); + +pub struct CurrentDateTime; + +impl Tool for CurrentDateTime { + const NAME: &'static str = "current_datetime"; + type Error = CurrentDateTimeError; + type Args = CurrentDateTimeArgs; + type Output = CurrentDateTimeOutput; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: Self::NAME.to_string(), + description: "Returns the current UTC date and time, plus the corresponding Unix \ + timestamp in seconds. Takes no arguments." + .to_string(), + parameters: json!({ + "type": "object", + "properties": {}, + "required": [] + }), + } + } + + async fn call(&self, _args: Self::Args) -> Result { + let now = Utc::now(); + Ok(CurrentDateTimeOutput { + utc: now.to_rfc3339(), + unix_seconds: now.timestamp(), + }) + } +} diff --git a/rs/ai_agent/src/tools/mod.rs b/rs/ai_agent/src/tools/mod.rs new file mode 100644 index 000000000000..dc32f0265175 --- /dev/null +++ b/rs/ai_agent/src/tools/mod.rs @@ -0,0 +1,15 @@ +//! Tool registry. +//! +//! v1 tools are statically defined Rust types implementing `rig::tool::Tool`. +//! New tools are added by creating a new sibling module and re-exporting it +//! here. The agent in `providers::AiProvider::build_agent` wires every entry +//! returned by [`registered_tool_names`] into the agent at construction +//! time. + +pub mod calculator; +pub mod current_datetime; +pub mod registry; + +pub use calculator::Calculator; +pub use current_datetime::CurrentDateTime; +pub use registry::{registered_tool_names, validate_tool_names}; diff --git a/rs/ai_agent/src/tools/registry.rs b/rs/ai_agent/src/tools/registry.rs new file mode 100644 index 000000000000..f70858e5eb35 --- /dev/null +++ b/rs/ai_agent/src/tools/registry.rs @@ -0,0 +1,24 @@ +//! Tool name registry. The actual tool *types* are wired into the agent +//! via [`crate::providers::AiProvider::build_agent`]; this module just +//! exposes their *names* so requests can be validated against the set of +//! supported tools. + +use rig::tool::Tool; + +use super::{Calculator, CurrentDateTime}; + +/// Returns the names of all built-in tools. +pub fn registered_tool_names() -> &'static [&'static str] { + &[Calculator::NAME, CurrentDateTime::NAME] +} + +/// Validates that every name in `requested` exists in the registry. +/// Returns the first unknown name on error. +pub fn validate_tool_names(requested: &[String]) -> Result<(), String> { + for name in requested { + if !registered_tool_names().contains(&name.as_str()) { + return Err(name.clone()); + } + } + Ok(()) +} diff --git a/rs/ic_os/config/tool/templates/ic.json5.template b/rs/ic_os/config/tool/templates/ic.json5.template index 8fe7e7da4045..992e4fba6778 100644 --- a/rs/ic_os/config/tool/templates/ic.json5.template +++ b/rs/ic_os/config/tool/templates/ic.json5.template @@ -231,9 +231,11 @@ chain OUTPUT {\n\ type filter hook output priority 0; policy accept;\n\ # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port\n\ - # 11434. This must come before the blanket 1-19999 reject below;\n\ - # nftables evaluates top-down, so the accept short-circuits the reject.\n\ + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These\n\ + # accepts must come before the blanket 1-19999 reject below; nftables\n\ + # evaluates top-down, so the accepts short-circuit the reject.\n\ meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434)\n\ + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500)\n\ meta skuid ic-http-adapter ip daddr { 127.0.0.0/8 } ct state { new } tcp dport { 1-19999 } reject # Block restricted localhost ic-http-adapter HTTPS access\n\ <>\n\ }\n\ @@ -285,6 +287,8 @@ table ip6 filter {\n\ ip6 saddr { hostos } ct state { new } tcp dport { 42372 } accept\n\ # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama).\n\ ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept\n\ + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes).\n\ + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept\n\ # Custom templated rules\n\ <>\n\ <>\n\ @@ -298,9 +302,11 @@ table ip6 filter {\n\ chain OUTPUT {\n\ type filter hook output priority 0; policy accept;\n\ # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port\n\ - # 11434. This must come before the blanket 1-19999 rejects below;\n\ - # nftables evaluates top-down, so the accept short-circuits the reject.\n\ + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These\n\ + # accepts must come before the blanket 1-19999 rejects below; nftables\n\ + # evaluates top-down, so the accepts short-circuit the rejects.\n\ meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434)\n\ + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500)\n\ meta skuid ic-http-adapter fib daddr type local ct state { new } tcp dport { 1-19999 } reject # Block restricted local addresses ic-http-adapter HTTPS access\n\ meta skuid ic-http-adapter ip6 daddr { 2a00:fb01:400:42::/64, 2602:fb2b:110::/48, 2602:fb2b:100::/48, 2602:fb2b:120::/48 } ct state { new } tcp dport { 1-19999 } reject # Block restricted outbound ic-http-adapter HTTPS access\n\ <>\n\ @@ -430,6 +436,8 @@ table ip6 filter {\n\ ip6 saddr { ::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff } ct state new tcp dport 443 accept\n\ # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama).\n\ ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept\n\ + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes).\n\ + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept\n\ \n\ <>\n\ <>\n\ From edf508d511cede2109b4f1452bc4f4e7322dcb16 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 4 May 2026 15:26:20 +0000 Subject: [PATCH 02/11] wip --- Cargo.Bazel.json.lock | 815 +++----------------------------- Cargo.Bazel.toml.lock | 106 +---- bazel/rust.MODULE.bazel | 58 +++ rs/ai_agent/BUILD.bazel | 1 + rs/ai_agent/Cargo.toml | 3 +- rs/ai_agent/src/config.rs | 6 +- rs/ai_agent/src/handlers/run.rs | 21 +- rs/ai_agent/src/main.rs | 14 + 8 files changed, 160 insertions(+), 864 deletions(-) diff --git a/Cargo.Bazel.json.lock b/Cargo.Bazel.json.lock index 5d78613758dd..eb3ab7bac04a 100644 --- a/Cargo.Bazel.json.lock +++ b/Cargo.Bazel.json.lock @@ -1,5 +1,5 @@ { - "checksum": "398b1dc766faff551d9d06595a0b31380325851db80ace08da152c1de5fcceb1", + "checksum": "50730c1bad9c51144f974cc56b36ae127cd53d03a5e7e45e300d1bab585124fe", "crates": { "abnf 0.12.0": { "name": "abnf", @@ -5128,204 +5128,6 @@ ], "license_file": "LICENSE-APACHE" }, - "aws-lc-rs 1.16.3": { - "name": "aws-lc-rs", - "version": "1.16.3", - "package_url": "https://github.com/aws/aws-lc-rs", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/aws-lc-rs/1.16.3/download", - "sha256": "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" - } - }, - "targets": [ - { - "Library": { - "crate_name": "aws_lc_rs", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "aws_lc_rs", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "aws-lc-sys", - "prebuilt-nasm" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "build_script_build" - }, - { - "id": "aws-lc-sys 0.40.0", - "target": "aws_lc_sys" - }, - { - "id": "zeroize 1.8.1", - "target": "zeroize" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "1.16.3" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ], - "link_deps": { - "common": [ - { - "id": "aws-lc-sys 0.40.0", - "target": "aws_lc_sys" - } - ], - "selects": {} - }, - "links": "aws_lc_rs_1_16_3_sys" - }, - "license": "ISC AND (Apache-2.0 OR ISC)", - "license_ids": [ - "Apache-2.0", - "ISC" - ], - "license_file": "LICENSE" - }, - "aws-lc-sys 0.40.0": { - "name": "aws-lc-sys", - "version": "0.40.0", - "package_url": "https://github.com/aws/aws-lc-rs", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/aws-lc-sys/0.40.0/download", - "sha256": "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" - } - }, - "targets": [ - { - "Library": { - "crate_name": "aws_lc_sys", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - }, - { - "BuildScript": { - "crate_name": "build_script_main", - "crate_root": "builder/main.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "aws_lc_sys", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "prebuilt-nasm" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "aws-lc-sys 0.40.0", - "target": "build_script_main" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.40.0" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "cc 1.2.48", - "target": "cc" - }, - { - "id": "cmake 0.1.58", - "target": "cmake" - }, - { - "id": "dunce 1.0.5", - "target": "dunce" - }, - { - "id": "fs_extra 1.3.0", - "target": "fs_extra" - } - ], - "selects": {} - }, - "links": "aws_lc_0_40_0" - }, - "license": "ISC AND (Apache-2.0 OR ISC) AND Apache-2.0 AND MIT AND BSD-3-Clause AND (Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR ISC OR MIT-0)", - "license_ids": [ - "Apache-2.0", - "BSD-3-Clause", - "ISC", - "MIT", - "MIT-0" - ], - "license_file": "LICENSE" - }, "axum 0.7.9": { "name": "axum", "version": "0.7.9", @@ -14623,54 +14425,6 @@ ], "license_file": null }, - "cmake 0.1.58": { - "name": "cmake", - "version": "0.1.58", - "package_url": "https://github.com/rust-lang/cmake-rs", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/cmake/0.1.58/download", - "sha256": "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" - } - }, - "targets": [ - { - "Library": { - "crate_name": "cmake", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "cmake", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "cc 1.2.48", - "target": "cc" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.1.58" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "cobs 0.3.0": { "name": "cobs", "version": "0.3.0", @@ -22471,9 +22225,13 @@ "target": "quickcheck" }, { - "id": "quinn 0.11.9", + "id": "quinn 0.11.5", "target": "quinn" }, + { + "id": "quinn-proto 0.11.7", + "target": "quinn_proto" + }, { "id": "quinn-udp 0.5.5", "target": "quinn_udp" @@ -30143,13 +29901,7 @@ "custom", "std" ], - "selects": { - "wasm32-unknown-unknown": [ - "js", - "js-sys", - "wasm-bindgen" - ] - } + "selects": {} }, "deps": { "common": [ @@ -30170,16 +29922,6 @@ "id": "libc 0.2.186", "target": "libc" } - ], - "wasm32-unknown-unknown": [ - { - "id": "js-sys 0.3.97", - "target": "js_sys" - }, - { - "id": "wasm-bindgen 0.2.120", - "target": "wasm_bindgen" - } ] } }, @@ -34309,19 +34051,15 @@ ], "selects": { "aarch64-apple-darwin": [ - "aws-lc-rs", "webpki-tokio" ], "aarch64-unknown-linux-gnu": [ - "aws-lc-rs", "webpki-tokio" ], "x86_64-apple-darwin": [ - "aws-lc-rs", "webpki-tokio" ], "x86_64-unknown-linux-gnu": [ - "aws-lc-rs", "webpki-tokio" ] } @@ -47385,46 +47123,6 @@ ], "license_file": "LICENSE-APACHE" }, - "lru-slab 0.1.2": { - "name": "lru-slab", - "version": "0.1.2", - "package_url": "https://github.com/Ralith/lru-slab", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/lru-slab/0.1.2/download", - "sha256": "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - } - }, - "targets": [ - { - "Library": { - "crate_name": "lru_slab", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "lru_slab", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "edition": "2021", - "version": "0.1.2" - }, - "license": "MIT OR Apache-2.0 OR Zlib", - "license_ids": [ - "Apache-2.0", - "MIT", - "Zlib" - ], - "license_file": "LICENSE-APACHE" - }, "lz4_flex 0.11.5": { "name": "lz4_flex", "version": "0.11.5", @@ -63120,14 +62818,14 @@ ], "license_file": "LICENSE-MIT" }, - "quinn 0.11.9": { + "quinn 0.11.5": { "name": "quinn", - "version": "0.11.9", + "version": "0.11.5", "package_url": "https://github.com/quinn-rs/quinn", "repository": { "Http": { - "url": "https://static.crates.io/crates/quinn/0.11.9/download", - "sha256": "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" + "url": "https://static.crates.io/crates/quinn/0.11.5/download", + "sha256": "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" } }, "targets": [ @@ -63142,18 +62840,6 @@ ] } } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } } ], "library_target_name": "quinn", @@ -63166,8 +62852,7 @@ "log", "ring", "runtime-tokio", - "rustls", - "rustls-ring" + "rustls" ], "selects": {} }, @@ -63182,11 +62867,7 @@ "target": "pin_project_lite" }, { - "id": "quinn 0.11.9", - "target": "build_script_build" - }, - { - "id": "quinn-proto 0.11.14", + "id": "quinn-proto 0.11.7", "target": "quinn_proto", "alias": "proto" }, @@ -63204,7 +62885,11 @@ "target": "rustls" }, { - "id": "thiserror 2.0.18", + "id": "socket2 0.5.9", + "target": "socket2" + }, + { + "id": "thiserror 1.0.68", "target": "thiserror" }, { @@ -63216,43 +62901,10 @@ "target": "tracing" } ], - "selects": { - "cfg(all(target_family = \"wasm\", target_os = \"unknown\"))": [ - { - "id": "web-time 1.1.0", - "target": "web_time" - } - ], - "cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))": [ - { - "id": "socket2 0.5.9", - "target": "socket2" - } - ] - } + "selects": {} }, "edition": "2021", - "version": "0.11.9" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "cfg_aliases 0.2.1", - "target": "cfg_aliases" - } - ], - "selects": {} - } + "version": "0.11.5" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -63261,14 +62913,14 @@ ], "license_file": "LICENSE-APACHE" }, - "quinn-proto 0.11.14": { + "quinn-proto 0.11.7": { "name": "quinn-proto", - "version": "0.11.14", + "version": "0.11.7", "package_url": "https://github.com/quinn-rs/quinn", "repository": { "Http": { - "url": "https://static.crates.io/crates/quinn-proto/0.11.14/download", - "sha256": "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" + "url": "https://static.crates.io/crates/quinn-proto/0.11.7/download", + "sha256": "ea0a9b3a42929fad8a7c3de7f86ce0814cfa893328157672680e9fb1145549c5" } }, "targets": [ @@ -63292,9 +62944,10 @@ ], "crate_features": { "common": [ + "default", "log", "ring", - "rustls-ring" + "rustls" ], "selects": {} }, @@ -63305,11 +62958,7 @@ "target": "bytes" }, { - "id": "lru-slab 0.1.2", - "target": "lru_slab" - }, - { - "id": "rand 0.9.0", + "id": "rand 0.8.5", "target": "rand" }, { @@ -63329,7 +62978,7 @@ "target": "slab" }, { - "id": "thiserror 2.0.18", + "id": "thiserror 1.0.68", "target": "thiserror" }, { @@ -63341,25 +62990,10 @@ "target": "tracing" } ], - "selects": { - "cfg(all(target_family = \"wasm\", target_os = \"unknown\"))": [ - { - "id": "getrandom 0.3.1", - "target": "getrandom" - }, - { - "id": "rustls-pki-types 1.12.0", - "target": "rustls_pki_types" - }, - { - "id": "web-time 1.1.0", - "target": "web_time" - } - ] - } + "selects": {} }, "edition": "2021", - "version": "0.11.14" + "version": "0.11.7" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -67357,13 +66991,12 @@ "crate_features": { "common": [ "__rustls", - "__rustls-aws-lc-rs", "__tls", "charset", "http2", "json", "multipart", - "rustls", + "rustls-no-provider", "stream", "system-proxy" ], @@ -67422,30 +67055,10 @@ "id": "h2 0.4.4", "target": "h2" }, - { - "id": "hyper-rustls 0.27.7", - "target": "hyper_rustls" - }, { "id": "mime 0.3.17", "target": "mime" }, - { - "id": "rustls 0.23.27", - "target": "rustls" - }, - { - "id": "rustls-pki-types 1.12.0", - "target": "rustls_pki_types" - }, - { - "id": "rustls-platform-verifier 0.6.2", - "target": "rustls_platform_verifier" - }, - { - "id": "tokio-rustls 0.26.0", - "target": "tokio_rustls" - }, { "id": "tokio-util 0.7.17", "target": "tokio_util" @@ -67460,30 +67073,10 @@ "id": "h2 0.4.4", "target": "h2" }, - { - "id": "hyper-rustls 0.27.7", - "target": "hyper_rustls" - }, { "id": "mime 0.3.17", "target": "mime" }, - { - "id": "rustls 0.23.27", - "target": "rustls" - }, - { - "id": "rustls-pki-types 1.12.0", - "target": "rustls_pki_types" - }, - { - "id": "rustls-platform-verifier 0.6.2", - "target": "rustls_platform_verifier" - }, - { - "id": "tokio-rustls 0.26.0", - "target": "tokio_rustls" - }, { "id": "tokio-util 0.7.17", "target": "tokio_util" @@ -67568,30 +67161,10 @@ "id": "h2 0.4.4", "target": "h2" }, - { - "id": "hyper-rustls 0.27.7", - "target": "hyper_rustls" - }, { "id": "mime 0.3.17", "target": "mime" }, - { - "id": "rustls 0.23.27", - "target": "rustls" - }, - { - "id": "rustls-pki-types 1.12.0", - "target": "rustls_pki_types" - }, - { - "id": "rustls-platform-verifier 0.6.2", - "target": "rustls_platform_verifier" - }, - { - "id": "tokio-rustls 0.26.0", - "target": "tokio_rustls" - }, { "id": "tokio-util 0.7.17", "target": "tokio_util" @@ -67606,30 +67179,10 @@ "id": "h2 0.4.4", "target": "h2" }, - { - "id": "hyper-rustls 0.27.7", - "target": "hyper_rustls" - }, { "id": "mime 0.3.17", "target": "mime" }, - { - "id": "rustls 0.23.27", - "target": "rustls" - }, - { - "id": "rustls-pki-types 1.12.0", - "target": "rustls_pki_types" - }, - { - "id": "rustls-platform-verifier 0.6.2", - "target": "rustls_platform_verifier" - }, - { - "id": "tokio-rustls 0.26.0", - "target": "tokio_rustls" - }, { "id": "tokio-util 0.7.17", "target": "tokio_util" @@ -67637,6 +67190,16 @@ ] } }, + "extra_deps": { + "common": [ + "@crate_index__hyper-rustls-0.27.7//:hyper_rustls", + "@crate_index__rustls-0.23.27//:rustls", + "@crate_index__rustls-pki-types-1.12.0//:rustls_pki_types", + "@crate_index__rustls-platform-verifier-0.6.2//:rustls_platform_verifier", + "@crate_index__tokio-rustls-0.26.0//:tokio_rustls" + ], + "selects": {} + }, "edition": "2021", "version": "0.13.3" }, @@ -68008,9 +67571,7 @@ ], "crate_features": { "common": [ - "default", - "reqwest", - "rustls" + "reqwest" ], "selects": {} }, @@ -68312,11 +67873,7 @@ "dev_urandom_fallback", "std" ], - "selects": { - "wasm32-unknown-unknown": [ - "wasm32_unknown_unknown_js" - ] - } + "selects": {} }, "deps": { "common": [ @@ -70556,24 +70113,7 @@ "std", "tls12" ], - "selects": { - "aarch64-apple-darwin": [ - "aws-lc-rs", - "aws_lc_rs" - ], - "aarch64-unknown-linux-gnu": [ - "aws-lc-rs", - "aws_lc_rs" - ], - "x86_64-apple-darwin": [ - "aws-lc-rs", - "aws_lc_rs" - ], - "x86_64-unknown-linux-gnu": [ - "aws-lc-rs", - "aws_lc_rs" - ] - } + "selects": {} }, "deps": { "common": [ @@ -70619,32 +70159,7 @@ "target": "zeroize" } ], - "selects": { - "aarch64-apple-darwin": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "aarch64-unknown-linux-gnu": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "x86_64-apple-darwin": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "x86_64-unknown-linux-gnu": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ] - } + "selects": {} }, "edition": "2021", "version": "0.23.27" @@ -70666,32 +70181,7 @@ "target": "ring" } ], - "selects": { - "aarch64-apple-darwin": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "aarch64-unknown-linux-gnu": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "x86_64-apple-darwin": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "x86_64-unknown-linux-gnu": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ] - } + "selects": {} } }, "license": "Apache-2.0 OR ISC OR MIT", @@ -71113,12 +70603,7 @@ "default", "std" ], - "selects": { - "wasm32-unknown-unknown": [ - "web", - "web-time" - ] - } + "selects": {} }, "deps": { "common": [ @@ -71127,14 +70612,7 @@ "target": "zeroize" } ], - "selects": { - "wasm32-unknown-unknown": [ - { - "id": "web-time 1.1.0", - "target": "web_time" - } - ] - } + "selects": {} }, "edition": "2021", "version": "1.12.0" @@ -71458,20 +70936,7 @@ "ring", "std" ], - "selects": { - "aarch64-apple-darwin": [ - "aws-lc-rs" - ], - "aarch64-unknown-linux-gnu": [ - "aws-lc-rs" - ], - "x86_64-apple-darwin": [ - "aws-lc-rs" - ], - "x86_64-unknown-linux-gnu": [ - "aws-lc-rs" - ] - } + "selects": {} }, "deps": { "common": [ @@ -71489,32 +70954,7 @@ "target": "untrusted" } ], - "selects": { - "aarch64-apple-darwin": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "aarch64-unknown-linux-gnu": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "x86_64-apple-darwin": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ], - "x86_64-unknown-linux-gnu": [ - { - "id": "aws-lc-rs 1.16.3", - "target": "aws_lc_rs" - } - ] - } + "selects": {} }, "edition": "2021", "version": "0.103.3" @@ -84051,24 +83491,7 @@ "ring", "tls12" ], - "selects": { - "aarch64-apple-darwin": [ - "aws-lc-rs", - "aws_lc_rs" - ], - "aarch64-unknown-linux-gnu": [ - "aws-lc-rs", - "aws_lc_rs" - ], - "x86_64-apple-darwin": [ - "aws-lc-rs", - "aws_lc_rs" - ], - "x86_64-unknown-linux-gnu": [ - "aws-lc-rs", - "aws_lc_rs" - ] - } + "selects": {} }, "deps": { "common": [ @@ -84451,65 +83874,6 @@ ], "license_file": "LICENSE" }, - "tokio-tungstenite 0.23.1": { - "name": "tokio-tungstenite", - "version": "0.23.1", - "package_url": "https://github.com/snapview/tokio-tungstenite", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/tokio-tungstenite/0.23.1/download", - "sha256": "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" - } - }, - "targets": [ - { - "Library": { - "crate_name": "tokio_tungstenite", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "tokio_tungstenite", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "futures-util 0.3.32", - "target": "futures_util" - }, - { - "id": "log 0.4.28", - "target": "log" - }, - { - "id": "tokio 1.52.1", - "target": "tokio" - }, - { - "id": "tungstenite 0.23.0", - "target": "tungstenite" - } - ], - "selects": {} - }, - "edition": "2018", - "version": "0.23.1" - }, - "license": "MIT", - "license_ids": [ - "MIT" - ], - "license_file": "LICENSE" - }, "tokio-tungstenite 0.26.2": { "name": "tokio-tungstenite", "version": "0.26.2", @@ -87638,74 +87002,6 @@ ], "license_file": "LICENSE-APACHE" }, - "tungstenite 0.23.0": { - "name": "tungstenite", - "version": "0.23.0", - "package_url": "https://github.com/snapview/tungstenite-rs", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/tungstenite/0.23.0/download", - "sha256": "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" - } - }, - "targets": [ - { - "Library": { - "crate_name": "tungstenite", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "tungstenite", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "byteorder 1.5.0", - "target": "byteorder" - }, - { - "id": "bytes 1.11.1", - "target": "bytes" - }, - { - "id": "log 0.4.28", - "target": "log" - }, - { - "id": "rand 0.8.5", - "target": "rand" - }, - { - "id": "thiserror 1.0.68", - "target": "thiserror" - }, - { - "id": "utf-8 0.7.6", - "target": "utf8" - } - ], - "selects": {} - }, - "edition": "2018", - "version": "0.23.0" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "tungstenite 0.26.2": { "name": "tungstenite", "version": "0.26.2", @@ -100485,12 +99781,6 @@ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu" ], - "cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-unknown-linux-gnu" - ], "cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))": [ "aarch64-apple-darwin", "aarch64-unknown-linux-gnu", @@ -100835,7 +100125,8 @@ "publicsuffix 2.2.3", "qrcode 0.14.1", "quickcheck 1.0.3", - "quinn 0.11.9", + "quinn 0.11.5", + "quinn-proto 0.11.7", "quinn-udp 0.5.5", "quote 1.0.42", "rand 0.8.5", diff --git a/Cargo.Bazel.toml.lock b/Cargo.Bazel.toml.lock index e754719547e9..595d748766c1 100644 --- a/Cargo.Bazel.toml.lock +++ b/Cargo.Bazel.toml.lock @@ -905,28 +905,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "axum" version = "0.7.9" @@ -2467,15 +2445,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - [[package]] name = "cobs" version = "0.3.0" @@ -3882,6 +3851,7 @@ dependencies = [ "qrcode", "quickcheck", "quinn", + "quinn-proto", "quinn-udp", "quote", "rand 0.8.5", @@ -5184,10 +5154,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -8186,12 +8154,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "lz4_flex" version = "0.11.5" @@ -10725,44 +10687,37 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", - "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", "rustls 0.23.27", "socket2 0.5.9", - "thiserror 2.0.18", + "thiserror 1.0.68", "tokio", "tracing", - "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "ea0a9b3a42929fad8a7c3de7f86ce0814cfa893328157672680e9fb1145549c5" dependencies = [ - "aws-lc-rs", "bytes", - "getrandom 0.3.1", - "lru-slab", - "rand 0.9.0", + "rand 0.8.5", "ring 0.17.14", "rustc-hash 2.1.1", "rustls 0.23.27", - "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror 1.0.68", "tinyvec", "tracing", - "web-time", ] [[package]] @@ -11399,15 +11354,10 @@ dependencies = [ "mime_guess", "percent-encoding", "pin-project-lite", - "quinn", - "rustls 0.23.27", - "rustls-pki-types", - "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.0", "tokio-util", "tower 0.5.2", "tower-http 0.6.8", @@ -11505,7 +11455,6 @@ dependencies = [ "serde_json", "thiserror 2.0.18", "tokio", - "tokio-tungstenite 0.23.1", "tracing", "tracing-futures", "url", @@ -11865,7 +11814,6 @@ version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ - "aws-lc-rs", "brotli 8.0.1", "brotli-decompressor 5.0.0", "log", @@ -11952,7 +11900,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "web-time", "zeroize", ] @@ -12010,7 +11957,6 @@ version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ - "aws-lc-rs", "ring 0.17.14", "rustls-pki-types", "untrusted 0.9.0", @@ -14140,22 +14086,6 @@ dependencies = [ "tungstenite 0.21.0", ] -[[package]] -name = "tokio-tungstenite" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" -dependencies = [ - "futures-util", - "log", - "rustls 0.23.27", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.0", - "tungstenite 0.23.0", - "webpki-roots 0.26.8", -] - [[package]] name = "tokio-tungstenite" version = "0.26.2" @@ -14723,26 +14653,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "tungstenite" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.3.1", - "httparse", - "log", - "rand 0.8.5", - "rustls 0.23.27", - "rustls-pki-types", - "sha1", - "thiserror 1.0.68", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.26.2" diff --git a/bazel/rust.MODULE.bazel b/bazel/rust.MODULE.bazel index 4fbf909bf44a..a71d1fd56b46 100644 --- a/bazel/rust.MODULE.bazel +++ b/bazel/rust.MODULE.bazel @@ -1225,6 +1225,21 @@ crate.spec( package = "quinn", version = "^0.11.5", ) +crate.spec( + # Pinned at =0.11.7 deliberately. Newer point releases (0.11.13+) added + # an unconditional `ring = { features = ["wasm32_unknown_unknown_js"] }` + # and `getrandom = { features = ["wasm_js"] }` for `wasm32-unknown-unknown`. + # Bazel's crate_universe target-unifies features per package, so those + # transitively activate `getrandom 0.2.10/js` (= wasm-bindgen + js-sys) + # for *every* wasm32 crate in the workspace -- which causes IC canister + # wasms to fail Wasm-validation with `Module imports function + # '__wbindgen_describe' from '__wbindgen_placeholder__' that is not + # exported by the runtime`. Holding quinn-proto at 0.11.7 keeps the + # NNS canister Wasm clean while letting the host-side reqwest 0.12 + # (which transitively pulls quinn) keep working. + package = "quinn-proto", + version = "=0.11.7", +) crate.spec( package = "quinn-udp", version = "^0.5.5", @@ -1309,6 +1324,14 @@ crate.spec( version = "^0.17.7", ) crate.spec( + # See ai_agent docs / commit history for the full saga. Short version: + # we keep rig at default-features = false, features = ["reqwest"], and + # supply the missing TLS plumbing for reqwest 0.13 via the + # `rustls-no-provider` feature -- not by enabling rig's `rustls` + # feature (that drags in aws-lc-rs and conflicts with our pinned + # quinn 0.11.5). + default_features = False, + features = ["reqwest"], package = "rig-core", version = "^0.36.0", ) @@ -2069,6 +2092,41 @@ crate.annotation( crate = "libz-sys", crate_features = ["static"], ) +crate.annotation( + # rig 0.36 transitively builds reqwest 0.13, but rig only enables + # reqwest's `charset, http2, system-proxy` features -- never any TLS + # backend. As a result, reqwest 0.13's `Client::builder().build()` + # falls back to the plain-HTTP hyper connector and every https:// + # request fails synchronously with `client error (Connect) -> + # invalid URL, scheme is not http`. + # + # Activate `rustls-no-provider` (which gates the rustls connector + # code in reqwest source) *and* also inject the rustls deps the + # feature would normally pull in -- `crate.annotation crate_features` + # only sets compile-time `cfg(feature = ...)` flags; it does NOT + # re-trigger Cargo's dep resolution, so we have to wire the deps in + # by hand via the `deps` attribute. We deliberately use + # `rustls-no-provider` (not `rustls`) so that aws-lc-rs is not + # pulled in alongside the workspace's existing `ring` rustls + # provider; the ai_agent main() installs the ring provider at + # startup so reqwest's rustls path picks it up. + crate = "reqwest", + crate_features = [ + "__rustls", + "__tls", + "rustls-no-provider", + ], + deps = [ + "@crate_index__hyper-rustls-0.27.7//:hyper_rustls", + "@crate_index__rustls-0.23.27//:rustls", + "@crate_index__rustls-pki-types-1.12.0//:rustls_pki_types", + "@crate_index__rustls-platform-verifier-0.6.2//:rustls_platform_verifier", + "@crate_index__tokio-rustls-0.26.0//:tokio_rustls", + ], + version = "0.13.3", +) + + crate.annotation( crate = "curve25519-dalek", rustc_flags = [ diff --git a/rs/ai_agent/BUILD.bazel b/rs/ai_agent/BUILD.bazel index 7fdcc94e2da5..867e327c2ed9 100644 --- a/rs/ai_agent/BUILD.bazel +++ b/rs/ai_agent/BUILD.bazel @@ -40,5 +40,6 @@ rust_binary( deps = DEPENDENCIES + [ ":ai_agent", "@crate_index//:clap", + "@crate_index//:rustls", ], ) diff --git a/rs/ai_agent/Cargo.toml b/rs/ai_agent/Cargo.toml index 766572f036f5..bb16fcfb5ec5 100644 --- a/rs/ai_agent/Cargo.toml +++ b/rs/ai_agent/Cargo.toml @@ -15,7 +15,8 @@ anyhow = { workspace = true } axum = { workspace = true } clap = { workspace = true } meval = "0.2" -rig-core = "0.36" +rig-core = { version = "0.36", default-features = false, features = ["reqwest"] } +rustls = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } slog = { workspace = true } diff --git a/rs/ai_agent/src/config.rs b/rs/ai_agent/src/config.rs index 90038f072fb7..a38ba5e6e9f5 100644 --- a/rs/ai_agent/src/config.rs +++ b/rs/ai_agent/src/config.rs @@ -7,7 +7,11 @@ use serde::{Deserialize, Serialize}; /// Default Gemini model used if `/v1/config` doesn't override it. -pub const DEFAULT_GEMINI_MODEL: &str = "gemini-2.0-flash"; +/// +/// `gemini-2.0-flash` was retired for new users; switching to `flash-latest` +/// keeps the agent working without pinning to a specific (and deprecatable) +/// version. Callers can still override per-config via the `model` field. +pub const DEFAULT_GEMINI_MODEL: &str = "gemini-flash-latest"; /// Default agent system prompt. pub const DEFAULT_PREAMBLE: &str = "You are a concise, helpful assistant. \ diff --git a/rs/ai_agent/src/handlers/run.rs b/rs/ai_agent/src/handlers/run.rs index cc6c398eafd4..9be2cb126fc5 100644 --- a/rs/ai_agent/src/handlers/run.rs +++ b/rs/ai_agent/src/handlers/run.rs @@ -11,6 +11,17 @@ use crate::{ tools::validate_tool_names, }; +/// Render the full `Error::source()` chain of `e` as " -> "-separated text. +fn error_chain(e: &dyn std::error::Error) -> String { + let mut parts = Vec::new(); + let mut cur: Option<&dyn std::error::Error> = e.source(); + while let Some(s) = cur { + parts.push(s.to_string()); + cur = s.source(); + } + parts.join(" -> ") +} + /// `POST /v1/agent/run` — single-turn agent invocation. pub async fn run( State(state): State>, @@ -79,8 +90,14 @@ pub async fn run( (StatusCode::OK, Json(body)).into_response() } Err(e) => { - warn!(state.log, "agent run failed"; "error" => %e); - let msg = format!("Agent failed: {e}"); + // rig wraps the underlying reqwest/rustls error in + // `CompletionError::HttpError`, whose Display impl drops the + // source chain. Walk `Error::source()` ourselves so the + // failing transport-level cause makes it into the log / + // response body where it can actually be debugged. + let chain = error_chain(&e); + warn!(state.log, "agent run failed"; "error" => %e, "chain" => &chain); + let msg = format!("Agent failed: {e}: {chain}"); (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorBody::new(msg))).into_response() } } diff --git a/rs/ai_agent/src/main.rs b/rs/ai_agent/src/main.rs index 43e795546da9..f26a9063ddbc 100644 --- a/rs/ai_agent/src/main.rs +++ b/rs/ai_agent/src/main.rs @@ -29,6 +29,20 @@ async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); let log = make_logger(); + // Install the process-wide rustls crypto provider exactly once. + // + // rig 0.36 transitively pulls reqwest 0.13, which we configure with + // `rustls-no-provider` to avoid linking aws-lc-rs alongside the + // workspace's existing ring provider (mixing the two crashes rustls + // 0.23 at startup with `no process-level CryptoProvider available`). + // The flip side of `rustls-no-provider` is that *no* provider is + // auto-installed -- so we have to do it ourselves before the first + // `reqwest::Client::builder().build()` call inside rig. Without + // this, every Gemini request fails synchronously with the opaque + // `error sending request for url (...)` you see wrapped by rig as + // `CompletionError::HttpError`. + let _ = rustls::crypto::ring::default_provider().install_default(); + let config = AppConfig::default(); let state = Arc::new(AppState::new(config, log.clone())); let app = build_router(state); From 7a73615541245b034e3a7d1b22acf25a538427d8 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 4 May 2026 20:36:01 +0000 Subject: [PATCH 03/11] adding tools --- Cargo.lock | 311 +++++++++--- rs/ai_agent/BUILD.bazel | 14 + rs/ai_agent/Cargo.toml | 18 +- rs/ai_agent/src/config.rs | 29 +- rs/ai_agent/src/handlers/chat.rs | 2 +- rs/ai_agent/src/handlers/run.rs | 2 +- rs/ai_agent/src/main.rs | 19 +- rs/ai_agent/src/providers/mod.rs | 37 +- rs/ai_agent/src/state.rs | 52 +- rs/ai_agent/src/tools/ic_logs.rs | 447 ++++++++++++++++ rs/ai_agent/src/tools/ic_metrics.rs | 649 ++++++++++++++++++++++++ rs/ai_agent/src/tools/ic_state.rs | 536 +++++++++++++++++++ rs/ai_agent/src/tools/mod.rs | 7 + rs/ai_agent/src/tools/node_directory.rs | 137 +++++ rs/ai_agent/src/tools/registry.rs | 10 +- 15 files changed, 2195 insertions(+), 75 deletions(-) create mode 100644 rs/ai_agent/src/tools/ic_logs.rs create mode 100644 rs/ai_agent/src/tools/ic_metrics.rs create mode 100644 rs/ai_agent/src/tools/ic_state.rs create mode 100644 rs/ai_agent/src/tools/node_directory.rs diff --git a/Cargo.lock b/Cargo.lock index d90fa9eed896..e03cc5b803c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2469,7 +2469,7 @@ checksum = "1678b3295890df5895480a7c080430e73df2b7101f1763f62a3b32dd532f1d37" dependencies = [ "chrono", "http 1.4.0", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "serde_urlencoded", @@ -2646,7 +2646,7 @@ dependencies = [ "network", "once_cell", "regex", - "reqwest", + "reqwest 0.12.28", "rust-ini", "securefmt", "serde", @@ -2763,7 +2763,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "registry-canister", - "reqwest", + "reqwest 0.12.28", "serde_json", "slog", "tokio", @@ -4020,7 +4020,7 @@ dependencies = [ "k256 0.11.6", "keyring", "lazy_static", - "reqwest", + "reqwest 0.12.28", "ring", "schemars 0.8.22", "sec1 0.3.0", @@ -4671,6 +4671,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom 7.1.3", + "pin-project-lite", +] + [[package]] name = "evm_rpc_client" version = "0.4.0" @@ -4779,7 +4790,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "registry-canister", - "reqwest", + "reqwest 0.12.28", "serde", "serde_cbor", "slog", @@ -6504,7 +6515,7 @@ dependencies = [ "pkcs8 0.10.2", "rand 0.8.5", "rangemap", - "reqwest", + "reqwest 0.12.28", "ring", "sec1 0.7.3", "serde", @@ -6520,6 +6531,42 @@ dependencies = [ "url", ] +[[package]] +name = "ic-ai-agent" +version = "0.9.0" +dependencies = [ + "anyhow", + "axum 0.8.8", + "chrono", + "clap 4.6.0", + "hex", + "ic-base-types", + "ic-config", + "ic-interfaces-registry", + "ic-protobuf", + "ic-registry-client", + "ic-registry-client-helpers", + "ic-registry-local-store", + "ic-state-layout", + "ic-types", + "lru 0.7.8", + "meval", + "prometheus-parse", + "reqwest 0.12.28", + "rig-core", + "rustls 0.23.37", + "serde", + "serde_json", + "slog", + "slog-async", + "slog-term", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tower 0.5.3", + "tower-http", +] + [[package]] name = "ic-artifact-downloader" version = "0.9.0" @@ -6640,7 +6687,7 @@ dependencies = [ "ic-test-utilities-tmpdir", "ic-types", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "rstest", "serde", "serde_json", @@ -6760,7 +6807,7 @@ dependencies = [ "rand 0.8.5", "rcgen 0.14.7", "regex", - "reqwest", + "reqwest 0.12.28", "rustls 0.23.37", "rustls-acme", "rustls-pemfile", @@ -6821,7 +6868,7 @@ dependencies = [ "parse-size", "prometheus", "rcgen 0.14.7", - "reqwest", + "reqwest 0.12.28", "rustls 0.23.37", "serde", "socket2 0.6.3", @@ -6890,7 +6937,7 @@ dependencies = [ "rate-limits-api", "ratelimit", "regex", - "reqwest", + "reqwest 0.12.28", "rustls 0.23.37", "salt-sharing-api", "serde", @@ -6928,7 +6975,7 @@ dependencies = [ "ic-crypto-tree-hash", "ic-system-test-driver", "ic-types", - "reqwest", + "reqwest 0.12.28", "serde", "serde_cbor", "slog", @@ -6952,7 +6999,7 @@ dependencies = [ "ic-system-test-driver", "prost 0.13.5", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "slog", "tokio", ] @@ -9807,7 +9854,7 @@ dependencies = [ "prometheus", "rand 0.8.5", "regex", - "reqwest", + "reqwest 0.12.28", "rustls 0.23.37", "serde", "serde_json", @@ -9902,7 +9949,7 @@ dependencies = [ "ic-metrics", "ic-test-utilities-logger", "prometheus", - "reqwest", + "reqwest 0.12.28", "slog", "tokio", "tower 0.5.3", @@ -9976,7 +10023,7 @@ dependencies = [ "prometheus", "proptest", "prost 0.13.5", - "reqwest", + "reqwest 0.12.28", "rstest", "rustls 0.23.37", "serde", @@ -10003,7 +10050,7 @@ dependencies = [ "ic-http-endpoints-public", "ic-types", "ic-validator", - "reqwest", + "reqwest 0.12.28", "serde_cbor", "tokio", "url", @@ -10038,7 +10085,7 @@ dependencies = [ "maplit", "prometheus", "prost 0.13.5", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "slog", @@ -10087,7 +10134,7 @@ dependencies = [ "ic-logger", "ic-test-utilities-in-memory-logger", "mockito", - "reqwest", + "reqwest 0.12.28", "slog", "tar", "tempfile", @@ -10295,7 +10342,7 @@ dependencies = [ "icrc-ledger-types", "num-bigint 0.4.6", "pocket-ic", - "reqwest", + "reqwest 0.12.28", "rosetta-core", "serde", "tempfile", @@ -10312,7 +10359,7 @@ dependencies = [ "ic-rosetta-test-utils", "icp-ledger", "pocket-ic", - "reqwest", + "reqwest 0.12.28", "tempfile", "tokio", ] @@ -10379,7 +10426,7 @@ dependencies = [ "prometheus-parse", "proptest", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "rolling-file", "rosetta-core", "rusqlite", @@ -10418,7 +10465,7 @@ dependencies = [ "num-bigint 0.4.6", "pocket-ic", "prometheus-parse", - "reqwest", + "reqwest 0.12.28", "rosetta-core", "serde", "serde_json", @@ -10434,7 +10481,7 @@ dependencies = [ "candid", "icrc-ledger-types", "pocket-ic", - "reqwest", + "reqwest 0.12.28", "tempfile", "tokio", ] @@ -10866,7 +10913,7 @@ dependencies = [ "indicatif", "on_wire", "proptest", - "reqwest", + "reqwest 0.12.28", "rosetta-core", "rusqlite", "serde", @@ -11135,7 +11182,7 @@ dependencies = [ "futures", "hex", "maplit", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -11339,7 +11386,7 @@ dependencies = [ "prometheus-parse", "rand 0.8.5", "registry-canister", - "reqwest", + "reqwest 0.12.28", "serde", "serde_bytes", "serde_cbor", @@ -12770,7 +12817,7 @@ dependencies = [ "pretty_assertions", "prost 0.13.5", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "slog", @@ -12948,7 +12995,7 @@ dependencies = [ "ic-test-utilities-types", "ic-types", "prost 0.13.5", - "reqwest", + "reqwest 0.12.28", "serde", "serde_cbor", "serde_json", @@ -13738,7 +13785,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "registry-canister", - "reqwest", + "reqwest 0.12.28", "rolling-file", "rosetta-core", "rusqlite", @@ -13770,7 +13817,7 @@ dependencies = [ "icp-ledger", "nix 0.24.3", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "rosetta-core", "serde", "serde_json", @@ -14430,7 +14477,7 @@ dependencies = [ "pocket-ic", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest", + "reqwest 0.12.28", "rust_decimal", "serde", "slog", @@ -14898,7 +14945,7 @@ dependencies = [ "rand_chacha 0.3.1", "regex", "registry-canister", - "reqwest", + "reqwest 0.12.28", "ring", "serde", "serde_bytes", @@ -15252,7 +15299,7 @@ dependencies = [ "once_cell", "rand 0.8.5", "registry-canister", - "reqwest", + "reqwest 0.12.28", "serde", "slog", "ssh2", @@ -15341,7 +15388,7 @@ dependencies = [ "ic_consensus_system_test_utils", "ic_consensus_threshold_sig_system_test_utils", "icrc-ledger-types", - "reqwest", + "reqwest 0.12.28", "serde_json", "slog", ] @@ -15803,7 +15850,7 @@ dependencies = [ "prometheus", "proptest", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "slog", "tempfile", "test-strategy 0.4.5", @@ -15993,7 +16040,7 @@ dependencies = [ "prost 0.13.5", "rand 0.8.5", "registry-canister", - "reqwest", + "reqwest 0.12.28", "rsa", "serde_json", "slog", @@ -16043,7 +16090,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "registry-canister", - "reqwest", + "reqwest 0.12.28", "serde_cbor", "serde_json", "slog", @@ -16120,7 +16167,7 @@ dependencies = [ "leb128", "p256", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "rsa", "serde", "serde_bytes", @@ -16517,7 +16564,7 @@ dependencies = [ "ic-types", "ic-universal-canister", "itertools 0.12.1", - "reqwest", + "reqwest 0.12.28", "serde_json", "slog", "ssh2", @@ -17725,6 +17772,9 @@ name = "lru" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +dependencies = [ + "hashbrown 0.12.3", +] [[package]] name = "lru" @@ -18036,6 +18086,16 @@ dependencies = [ "proptest", ] +[[package]] +name = "meval" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9" +dependencies = [ + "fnv", + "nom 1.2.4", +] + [[package]] name = "mime" version = "0.3.17" @@ -18100,9 +18160,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -18243,6 +18303,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "neli" version = "0.6.5" @@ -18294,7 +18363,7 @@ dependencies = [ "ic_consensus_system_test_utils", "prost 0.13.5", "regex", - "reqwest", + "reqwest 0.12.28", "serde_cbor", "slog", "tempfile", @@ -18364,7 +18433,7 @@ dependencies = [ "rand_chacha 0.3.1", "registry-canister", "rejoin-test-lib", - "reqwest", + "reqwest 0.12.28", "serde", "serde_cbor", "serde_json", @@ -18530,7 +18599,7 @@ dependencies = [ "on_wire", "prost 0.13.5", "registry-canister", - "reqwest", + "reqwest 0.12.28", "serde", "serde_cbor", "slog", @@ -18595,6 +18664,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" +[[package]] +name = "nom" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" + [[package]] name = "nom" version = "7.1.3" @@ -19282,6 +19357,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -19325,7 +19409,7 @@ dependencies = [ "ic-utils 0.45.0", "ic_consensus_system_test_utils", "itertools 0.12.1", - "reqwest", + "reqwest 0.12.28", "serde", "slog", "tempfile", @@ -19907,7 +19991,7 @@ dependencies = [ "maplit", "prost 0.13.5", "registry-canister", - "reqwest", + "reqwest 0.12.28", "schemars 0.8.22", "semver", "serde", @@ -20017,7 +20101,7 @@ dependencies = [ "prometheus", "rcgen 0.13.2", "registry-canister", - "reqwest", + "reqwest 0.12.28", "rustls 0.23.37", "serde", "serde_cbor", @@ -21592,11 +21676,50 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", "webpki-roots 1.0.6", ] +[[package]] +name = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + [[package]] name = "resolv-conf" version = "0.7.6" @@ -21679,6 +21802,38 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rig-core" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac7d75266ac1f79b1faeff611c85cb40be00a0a983db10036591424f0433dc1" +dependencies = [ + "as-any", + "async-stream", + "base64 0.22.1", + "bytes", + "eventsource-stream", + "fastrand", + "futures", + "futures-timer", + "glob", + "http 1.4.0", + "mime", + "mime_guess", + "nanoid", + "ordered-float 5.3.0", + "pin-project-lite", + "reqwest 0.13.3", + "schemars 1.2.1", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-futures", + "url", +] + [[package]] name = "ring" version = "0.17.14" @@ -21894,7 +22049,7 @@ dependencies = [ "icp-ledger", "on_wire", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "rosetta-core", "serde", "serde_cbor", @@ -22387,7 +22542,7 @@ checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ "dyn-clone", "indexmap 2.13.0", - "schemars_derive", + "schemars_derive 0.8.22", "serde", "serde_json", ] @@ -22412,6 +22567,7 @@ checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", + "schemars_derive 1.2.1", "serde", "serde_json", ] @@ -22428,6 +22584,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -22484,7 +22652,7 @@ dependencies = [ "ic-registry-subnet-type", "ic-system-test-driver", "ic_consensus_system_test_utils", - "reqwest", + "reqwest 0.12.28", "serde_json", "slog", ] @@ -23012,7 +23180,7 @@ dependencies = [ "mockall", "pem", "rcgen 0.13.2", - "reqwest", + "reqwest 0.12.28", "sev", "tempfile", "tokio", @@ -23846,7 +24014,7 @@ dependencies = [ "ic-nervous-system-common-test-keys", "ic-nns-common", "ic-nns-constants", - "reqwest", + "reqwest 0.12.28", "rgb", "serde", "serde_json", @@ -24397,9 +24565,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -24425,9 +24593,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -24876,6 +25044,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "futures", + "futures-task", + "pin-project", + "tracing", +] + [[package]] name = "tracing-journald" version = "0.3.2" @@ -25378,7 +25558,7 @@ dependencies = [ "lalrpop 0.22.2", "lz4_flex", "nom-language", - "ordered-float", + "ordered-float 4.6.0", "regex", "serde", "serde_json", @@ -25429,7 +25609,7 @@ dependencies = [ "ic-http-utils", "mockall", "procfs", - "reqwest", + "reqwest 0.12.28", "rusb", "serde", "serde_json", @@ -25772,6 +25952,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm_fuzzers" version = "0.9.0" diff --git a/rs/ai_agent/BUILD.bazel b/rs/ai_agent/BUILD.bazel index 867e327c2ed9..29c0d5ba289a 100644 --- a/rs/ai_agent/BUILD.bazel +++ b/rs/ai_agent/BUILD.bazel @@ -4,16 +4,30 @@ package(default_visibility = ["//visibility:public"]) DEPENDENCIES = [ # Keep sorted. + "//rs/config", + "//rs/interfaces/registry", + "//rs/protobuf", + "//rs/registry/client", + "//rs/registry/helpers", + "//rs/registry/local_store", + "//rs/state_layout", + "//rs/types/base_types", + "//rs/types/types", "@crate_index//:anyhow", "@crate_index//:axum", "@crate_index//:chrono", + "@crate_index//:hex", + "@crate_index//:lru", "@crate_index//:meval", + "@crate_index//:prometheus-parse", + "@crate_index//:reqwest", "@crate_index//:rig_core", "@crate_index//:serde", "@crate_index//:serde_json", "@crate_index//:slog", "@crate_index//:slog-async", "@crate_index//:slog-term", + "@crate_index//:tempfile", "@crate_index//:thiserror", "@crate_index//:tokio", "@crate_index//:tower", diff --git a/rs/ai_agent/Cargo.toml b/rs/ai_agent/Cargo.toml index bb16fcfb5ec5..9e07855c0d0f 100644 --- a/rs/ai_agent/Cargo.toml +++ b/rs/ai_agent/Cargo.toml @@ -13,8 +13,22 @@ path = "src/main.rs" [dependencies] anyhow = { workspace = true } axum = { workspace = true } -clap = { workspace = true } +chrono = { workspace = true } +clap = { workspace = true, features = ["env"] } +hex = { workspace = true } +ic-base-types = { path = "../types/base_types" } +ic-config = { path = "../config" } +ic-interfaces-registry = { path = "../interfaces/registry" } +ic-protobuf = { path = "../protobuf" } +ic-registry-client = { path = "../registry/client" } +ic-registry-client-helpers = { path = "../registry/helpers" } +ic-registry-local-store = { path = "../registry/local_store" } +ic-state-layout = { path = "../state_layout" } +ic-types = { path = "../types/types" } +lru = "0.7.8" meval = "0.2" +prometheus-parse = { workspace = true } +reqwest = { workspace = true } rig-core = { version = "0.36", default-features = false, features = ["reqwest"] } rustls = { workspace = true } serde = { workspace = true } @@ -22,8 +36,8 @@ serde_json = { workspace = true } slog = { workspace = true } slog-async = { workspace = true } slog-term = { workspace = true } +tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tower = { workspace = true } tower-http = { workspace = true } -chrono = { workspace = true } diff --git a/rs/ai_agent/src/config.rs b/rs/ai_agent/src/config.rs index a38ba5e6e9f5..22f189c35744 100644 --- a/rs/ai_agent/src/config.rs +++ b/rs/ai_agent/src/config.rs @@ -5,6 +5,7 @@ //! the spec). Defaults below cover the rest. use serde::{Deserialize, Serialize}; +use std::path::PathBuf; /// Default Gemini model used if `/v1/config` doesn't override it. /// @@ -14,13 +15,33 @@ use serde::{Deserialize, Serialize}; pub const DEFAULT_GEMINI_MODEL: &str = "gemini-flash-latest"; /// Default agent system prompt. +/// +/// IC observability tools (`ic_state`, `ic_metrics`, `ic_logs`) are described +/// here so the LLM knows to reach for them. Without an explicit mention, the +/// model tends to fall back to "I don't have access to live data" answers +/// instead of using the tools wired into the agent. pub const DEFAULT_PREAMBLE: &str = "You are a concise, helpful assistant. \ When tools are provided, prefer using them for any factual, computational, \ - or time-sensitive request."; + or time-sensitive request. \ + \ + You can also query Internet Computer node observability: `ic_state` for \ + canister/subnet/node metadata read from the locally synced state, \ + `ic_metrics` for replica/orchestrator/host Prometheus metrics, and \ + `ic_logs` for recent systemd journal output. Prefer `ic_state` for \ + \"what exists\", `ic_metrics` for \"how is it performing\", and \ + `ic_logs` for \"what happened recently\". Always cite the metric name \ + or log timestamp range you used."; /// Default cap on tool-call turns per agent invocation. pub const DEFAULT_MAX_TURNS: usize = 5; +/// Default replica config file path on a deployed GuestOS / AiNode. The +/// orchestrator places `ic.json5` here and we re-parse it (with a tmpdir for +/// any path-resolution helpers it does internally) to discover the on-disk +/// state root that the AiNode's state-sync replica writes to **and** the +/// registry local store path used to look up peer node IPv6 addresses. +pub const DEFAULT_IC_CONFIG_PATH: &str = "/run/ic-node/config/ic.json5"; + /// Static configuration baked in at startup. The actual provider client is /// created later, when `/v1/config` is invoked with the API key. #[derive(Clone, Debug)] @@ -28,6 +49,11 @@ pub struct AppConfig { pub default_model: String, pub default_preamble: String, pub default_max_turns: usize, + /// Path to the replica `ic.json5` config. Used by `ic_state` to discover + /// the on-disk state root, and by `ic_metrics` / `ic_logs` to discover + /// the registry local store (so node ids can be resolved to IPv6 + /// addresses of peer nodes in the syncing subnet). + pub ic_config_path: PathBuf, } impl Default for AppConfig { @@ -36,6 +62,7 @@ impl Default for AppConfig { default_model: DEFAULT_GEMINI_MODEL.to_string(), default_preamble: DEFAULT_PREAMBLE.to_string(), default_max_turns: DEFAULT_MAX_TURNS, + ic_config_path: PathBuf::from(DEFAULT_IC_CONFIG_PATH), } } } diff --git a/rs/ai_agent/src/handlers/chat.rs b/rs/ai_agent/src/handlers/chat.rs index 6b90fafa7091..d5daedcb2acf 100644 --- a/rs/ai_agent/src/handlers/chat.rs +++ b/rs/ai_agent/src/handlers/chat.rs @@ -55,7 +55,7 @@ pub async fn chat( .unwrap_or(state.config.default_preamble.as_str()); let max_turns = req.max_turns.unwrap_or(state.config.default_max_turns); - let agent = match provider.build_agent(preamble, &[]) { + let agent = match provider.build_agent(&state, preamble, &[]).await { Ok(a) => a, Err(e) => { return ( diff --git a/rs/ai_agent/src/handlers/run.rs b/rs/ai_agent/src/handlers/run.rs index 9be2cb126fc5..d3a27889b2f5 100644 --- a/rs/ai_agent/src/handlers/run.rs +++ b/rs/ai_agent/src/handlers/run.rs @@ -61,7 +61,7 @@ pub async fn run( .unwrap_or(state.config.default_preamble.as_str()); let max_turns = req.max_turns.unwrap_or(state.config.default_max_turns); - let agent = match provider.build_agent(preamble, &req.context) { + let agent = match provider.build_agent(&state, preamble, &req.context).await { Ok(a) => a, Err(e) => { return ( diff --git a/rs/ai_agent/src/main.rs b/rs/ai_agent/src/main.rs index f26a9063ddbc..a70f7d901c1a 100644 --- a/rs/ai_agent/src/main.rs +++ b/rs/ai_agent/src/main.rs @@ -5,9 +5,13 @@ //! a stunnel sidecar terminates TLS for external traffic. use clap::Parser; -use ic_ai_agent::{config::AppConfig, router::build_router, state::AppState}; +use ic_ai_agent::{ + config::{AppConfig, DEFAULT_IC_CONFIG_PATH}, + router::build_router, + state::AppState, +}; use slog::{Drain, Logger, info, o}; -use std::{net::SocketAddr, sync::Arc}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc}; #[derive(Debug, Parser)] #[command(name = "ic-ai-agent", about = "IC AI agent orchestration HTTP API")] @@ -15,6 +19,12 @@ struct Cli { /// Address to bind the HTTP server to. #[arg(long, env = "IC_AI_AGENT_ADDR", default_value = "127.0.0.1:11501")] addr: SocketAddr, + + /// Path to the replica `ic.json5` config. Used by `ic_state` to find + /// the on-disk state root, and by `ic_metrics` / `ic_logs` to find + /// the registry local store for resolving peer node IPv6 addresses. + #[arg(long, env = "IC_AI_AGENT_IC_CONFIG", default_value = DEFAULT_IC_CONFIG_PATH)] + ic_config: PathBuf, } fn make_logger() -> Logger { @@ -43,7 +53,10 @@ async fn main() -> anyhow::Result<()> { // `CompletionError::HttpError`. let _ = rustls::crypto::ring::default_provider().install_default(); - let config = AppConfig::default(); + let config = AppConfig { + ic_config_path: cli.ic_config.clone(), + ..AppConfig::default() + }; let state = Arc::new(AppState::new(config, log.clone())); let app = build_router(state); diff --git a/rs/ai_agent/src/providers/mod.rs b/rs/ai_agent/src/providers/mod.rs index f3d1967e9352..dd345b3ee00c 100644 --- a/rs/ai_agent/src/providers/mod.rs +++ b/rs/ai_agent/src/providers/mod.rs @@ -5,16 +5,20 @@ //! [`AiProvider`] with a new variant, add an arm in [`AiProvider::from_request`] //! and in [`AiProvider::build_agent`], done. +use std::sync::Arc; + use anyhow::anyhow; use rig::{ agent::Agent, client::CompletionClient, providers::gemini::{Client as GeminiClient, completion::CompletionModel as GeminiCompletion}, }; +use slog::warn; use crate::{ config::ConfigRequest, - tools::{Calculator, CurrentDateTime}, + state::AppState, + tools::{Calculator, CurrentDateTime, IcLogs, IcMetrics, IcState}, }; /// Active AI provider client. Currently Gemini-only; new providers slot in @@ -59,8 +63,17 @@ impl AiProvider { /// Build a configured agent. Tools and context can be filtered/added by /// the caller, but in v1 we always wire all built-in tools so requests /// can reference any of them by name. - pub fn build_agent( + /// + /// Takes `Arc` so the IC observability tools (`ic_state`, + /// `ic_metrics`, `ic_logs`) can pick up the shared replica config + /// path and lazily-built node directory. Tools that fail to + /// construct (typically `ic_state` on a node where `ic.json5` is + /// missing or unreadable) are skipped with a warning rather than + /// failing the whole agent build — the other tools may still be + /// useful and we don't want one bad path to take the agent down. + pub async fn build_agent( &self, + state: &Arc, preamble: &str, contexts: &[String], ) -> anyhow::Result> { @@ -70,8 +83,24 @@ impl AiProvider { for ctx in contexts { builder = builder.context(ctx); } - let agent = builder.tool(Calculator).tool(CurrentDateTime).build(); - Ok(agent) + let mut builder = builder.tool(Calculator).tool(CurrentDateTime); + + match IcState::new(state.clone()).await { + Ok(t) => builder = builder.tool(t), + Err(e) => { + warn!( + state.log, + "skipping ic_state tool: {}", e; + "ic_config_path" => %state.config.ic_config_path.display() + ); + } + } + + builder = builder + .tool(IcMetrics::new(state.clone())) + .tool(IcLogs::new(state.clone())); + + Ok(builder.build()) } } } diff --git a/rs/ai_agent/src/state.rs b/rs/ai_agent/src/state.rs index 0a95d17a6adf..248408570935 100644 --- a/rs/ai_agent/src/state.rs +++ b/rs/ai_agent/src/state.rs @@ -4,9 +4,14 @@ //! defaults. Wrapped in `Arc>` so `/v1/config` can update it //! at runtime without recreating the router. -use crate::{config::AppConfig, providers::AiProvider}; +use crate::{ + config::AppConfig, + providers::AiProvider, + tools::node_directory::{NodeDirectory, NodeDirectoryError}, +}; use slog::Logger; -use tokio::sync::RwLock; +use std::sync::Arc; +use tokio::sync::{OnceCell, RwLock}; /// Mutable runtime state shared across handlers. pub struct AppState { @@ -14,6 +19,11 @@ pub struct AppState { pub log: Logger, /// `None` until `POST /v1/config` populates it. pub provider: RwLock>, + /// Lazily constructed registry-backed node lookup. Built on first + /// use because (a) it touches the filesystem and we want + /// `AppState::new` to stay infallible, and (b) on a fresh AiNode + /// the registry local store may not exist yet on startup. + node_directory: OnceCell, NodeDirectoryError>>, } impl AppState { @@ -22,6 +32,44 @@ impl AppState { config, log, provider: RwLock::new(None), + node_directory: OnceCell::new(), } } + + /// Returns the shared node directory, constructing it on first + /// call. Errors are cached: if the registry local store is + /// misconfigured we don't keep retrying on every tool call. Restart + /// the service to retry. + pub async fn node_directory(&self) -> Result, NodeDirectoryError> { + let cfg_path = self.config.ic_config_path.clone(); + let res = self + .node_directory + .get_or_init(|| async move { + tokio::task::spawn_blocking(move || NodeDirectory::from_ic_config(&cfg_path)) + .await + .map_err(|e| NodeDirectoryError::Config { + path: std::path::PathBuf::new(), + message: format!("join error: {e}"), + })? + .map(Arc::new) + }) + .await; + match res { + Ok(d) => Ok(Arc::clone(d)), + Err(e) => Err(clone_node_directory_error(e)), + } + } +} + +/// `NodeDirectoryError` doesn't implement `Clone` (the underlying +/// `RegistryClientError` carries a non-cloneable source). For caching +/// purposes we render it through a string round-trip; the lossy +/// display is acceptable here because the cached error is only ever +/// surfaced to the LLM as a one-shot "couldn't reach the registry" +/// message. +fn clone_node_directory_error(e: &NodeDirectoryError) -> NodeDirectoryError { + NodeDirectoryError::Config { + path: std::path::PathBuf::new(), + message: e.to_string(), + } } diff --git a/rs/ai_agent/src/tools/ic_logs.rs b/rs/ai_agent/src/tools/ic_logs.rs new file mode 100644 index 000000000000..831d09d5bd8d --- /dev/null +++ b/rs/ai_agent/src/tools/ic_logs.rs @@ -0,0 +1,447 @@ +//! Tool: `ic_logs`. +//! +//! Pulls recent journald entries for an allow-listed systemd unit +//! through `systemd-journal-gatewayd`, which every IC node exposes on +//! port 19531 over HTTP. +//! +//! Gatewayd's journal API is documented at +//! . +//! In short: `GET /entries` with `Accept: application/json` returns +//! one JSON object per entry per line ("application/json-seq" with +//! ASCII-RS framing in newer versions, but most deployments still +//! emit plain newline-delimited JSON; we handle both). Filtering by +//! unit is done with a `_SYSTEMD_UNIT=...` query parameter; line +//! count via `Range: entries=:-N:N`. Time-bound and priority filtering +//! are applied client-side because gatewayd's native filter for those +//! is awkward to compose. + +use std::{net::Ipv6Addr, str::FromStr, sync::Arc, time::Duration}; + +use chrono::{DateTime, TimeZone, Utc}; +use rig::{completion::ToolDefinition, tool::Tool}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use crate::state::AppState; + +/// Default port `systemd-journal-gatewayd` listens on. +const GATEWAYD_PORT: u16 = 19531; + +/// HTTP timeout. Generous because gatewayd can be slow on big journals. +const FETCH_TIMEOUT: Duration = Duration::from_secs(20); + +/// Default lookback window when `since` is not supplied. +const DEFAULT_LOOKBACK: Duration = Duration::from_secs(15 * 60); + +/// Default and hard upper bound on lines fetched per call. +const DEFAULT_LINES: u32 = 200; +const MAX_LINES: u32 = 5000; + +/// Default priority ceiling (only entries with `PRIORITY <=` this are +/// returned). 6 = info; matches the spec. +const DEFAULT_PRIORITY: u8 = 6; + +/// Allow-list of systemd units the LLM may query. Keeping this short +/// and explicit is the only thing standing between the LLM and a +/// query like "show me the last 5000 lines of `ssh.service`". Adding +/// a unit here is a deliberate decision. +const ALLOWED_SERVICES: &[&str] = &[ + "ic-replica.service", + "ic-orchestrator.service", + "ic-crypto-csp.service", + "ic-https-outcalls-adapter.service", + "ic-btc-adapter.service", + "node_exporter.service", + "host_node_exporter.service", + "nftables.service", + "chrony.service", +]; + +#[derive(Debug, thiserror::Error)] +pub enum IcLogsError { + #[error("invalid arg: {0}")] + InvalidArg(String), + + #[error("http error: {0}")] + Http(#[from] reqwest::Error), + + #[error("upstream gatewayd returned {status}: {body}")] + Upstream { status: u16, body: String }, + + #[error("invalid timestamp '{value}': {source}")] + Timestamp { + value: String, + #[source] + source: chrono::ParseError, + }, + + #[error("node directory: {0}")] + Directory(#[from] crate::tools::node_directory::NodeDirectoryError), +} + +#[derive(Debug, Deserialize)] +pub struct LogsArgs { + /// systemd unit name. Must be in the built-in allow-list. + pub service: String, + + /// Lines to fetch from gatewayd before client-side filtering. + /// Default 200, capped at 5000. + pub lines: Option, + + /// RFC3339 lower bound. Default: now - 15 minutes. + pub since: Option, + + /// RFC3339 upper bound. Default: now. + pub until: Option, + + /// Min syslog priority (0=emerg .. 7=debug). Default 6 (info). + /// Entries with `PRIORITY > priority` are dropped. + pub priority: Option, + + /// Optional client-side substring filter on the message body + /// (literal `.contains()`, not regex). + pub grep: Option, + + /// Textual `NodeId` to query. Either this or `ipv6` is required. + pub node_id: Option, + + /// Raw IPv6 override (without brackets) for dev/test. + pub ipv6: Option, +} + +#[derive(Debug, Serialize)] +pub struct LogsOutput { + pub service: String, + pub returned: usize, + /// True if gatewayd had more entries within the time window than + /// we asked for. Lets the LLM know to widen `lines` or narrow the + /// time window. + pub truncated: bool, + pub target: TargetInfo, + pub entries: Vec, +} + +#[derive(Debug, Serialize)] +pub struct TargetInfo { + pub node_id: Option, + pub ipv6: String, + pub url: String, +} + +#[derive(Debug, Serialize)] +pub struct LogEntry { + /// RFC3339-formatted timestamp. + pub timestamp: String, + /// Syslog priority (0..=7), or 6 (info) if the entry didn't + /// declare one. + pub priority: u8, + pub unit: String, + pub message: String, +} + +/// Tool struct. +pub struct IcLogs { + state: Arc, + http: reqwest::Client, +} + +impl IcLogs { + pub fn new(state: Arc) -> Self { + let http = reqwest::Client::builder() + .timeout(FETCH_TIMEOUT) + .build() + .expect("reqwest client builder is infallible with default features"); + Self { state, http } + } + + async fn resolve_target( + &self, + node_id: Option<&str>, + ipv6: Option<&str>, + ) -> Result<(Ipv6Addr, Option), IcLogsError> { + if let Some(raw) = ipv6 { + let ip = Ipv6Addr::from_str(raw) + .map_err(|e| IcLogsError::InvalidArg(format!("invalid ipv6 '{raw}': {e}")))?; + return Ok((ip, None)); + } + let nid = node_id.ok_or_else(|| { + IcLogsError::InvalidArg("either node_id or ipv6 must be provided".to_string()) + })?; + let directory = self.state.node_directory().await?; + let ip = directory.resolve_ipv6(nid)?; + Ok((ip, Some(nid.to_string()))) + } +} + +/// Validate that `service` is on the allow-list. +fn check_allowed(service: &str) -> Result<(), IcLogsError> { + if !ALLOWED_SERVICES.contains(&service) { + return Err(IcLogsError::InvalidArg(format!( + "service '{service}' is not on the allow-list ({:?})", + ALLOWED_SERVICES + ))); + } + Ok(()) +} + +fn parse_rfc3339(s: &str) -> Result, IcLogsError> { + DateTime::parse_from_rfc3339(s) + .map(|dt| dt.with_timezone(&Utc)) + .map_err(|e| IcLogsError::Timestamp { + value: s.to_string(), + source: e, + }) +} + +/// Decode one journald-export-as-JSON object emitted by gatewayd into +/// our public `LogEntry`. Best-effort: gatewayd quotes strings except +/// for binary fields which it base64-encodes; our allow-listed +/// services don't emit binary messages so we treat all values as +/// strings. +fn decode_entry(v: &serde_json::Value) -> Option { + let obj = v.as_object()?; + + // Timestamp: gatewayd emits `__REALTIME_TIMESTAMP` as a string of + // microseconds-since-epoch. Newer versions also emit + // `__REALTIME_TIMESTAMP_USEC` and `__REALTIME_TIMESTAMP_NSEC`; we + // only need one. + let ts_us: i64 = obj + .get("__REALTIME_TIMESTAMP") + .and_then(|v| v.as_str()) + .and_then(|s| s.parse::().ok())?; + let secs = ts_us / 1_000_000; + let nsec = ((ts_us % 1_000_000) as u32) * 1_000; + let ts = Utc.timestamp_opt(secs, nsec).single()?; + + let priority: u8 = obj + .get("PRIORITY") + .and_then(|v| v.as_str()) + .and_then(|s| s.parse::().ok()) + .unwrap_or(DEFAULT_PRIORITY); + + let unit = obj + .get("_SYSTEMD_UNIT") + .or_else(|| obj.get("UNIT")) + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + let message = obj + .get("MESSAGE") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + Some(LogEntry { + timestamp: ts.to_rfc3339(), + priority, + unit, + message, + }) +} + +impl Tool for IcLogs { + const NAME: &'static str = "ic_logs"; + type Error = IcLogsError; + type Args = LogsArgs; + type Output = LogsOutput; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + // Inline the allow-list into the schema so the LLM sees the + // exact set of accepted services rather than just a free-form + // string field. + let services_json: Vec = ALLOWED_SERVICES + .iter() + .map(|s| serde_json::Value::String((*s).to_string())) + .collect(); + ToolDefinition { + name: Self::NAME.to_string(), + description: "Fetch recent systemd journal logs from one IC node's services. \ + Allowed services include `ic-replica.service`, `ic-orchestrator.service`, \ + and a few system units. Use this for \"what happened\" or \"why did X \ + fail\" questions. Returns structured log entries with timestamp, \ + priority, and message. Targets a specific peer node by `node_id` or \ + raw `ipv6`." + .to_string(), + parameters: json!({ + "type": "object", + "properties": { + "service": { + "type": "string", + "enum": services_json, + "description": "systemd unit to query." + }, + "lines": { + "type": "integer", + "minimum": 1, + "maximum": MAX_LINES, + "description": "Lines to fetch (default 200, max 5000)." + }, + "lines": { + "type": "integer", + "minimum": 1, + "maximum": MAX_LINES, + "description": "Lines to fetch (default 200, max 5000)." + }, + "since": { + "type": "string", + "description": "RFC3339 lower bound. Default: now - 15 minutes." + }, + "until": { + "type": "string", + "description": "RFC3339 upper bound. Default: now." + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 7, + "description": + "Min syslog priority (0=emerg .. 7=debug). Entries with \ + higher numeric priority are dropped. Default 6 (info)." + }, + "grep": { + "type": "string", + "description": + "Optional substring filter (literal, not regex)." + }, + "node_id": { + "type": "string", + "description": + "Textual NodeId of the peer to query. Resolved via the \ + local registry. Either node_id or ipv6 is required." + }, + "ipv6": { + "type": "string", + "description": + "Raw IPv6 of the peer (without brackets). Override for \ + dev/test." + } + }, + "required": ["service"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + check_allowed(&args.service)?; + + let lines = args.lines.unwrap_or(DEFAULT_LINES).clamp(1, MAX_LINES); + let priority = args.priority.unwrap_or(DEFAULT_PRIORITY); + if priority > 7 { + return Err(IcLogsError::InvalidArg(format!( + "priority must be 0..=7, got {priority}" + ))); + } + + let now = Utc::now(); + let since = match args.since.as_deref() { + Some(s) => parse_rfc3339(s)?, + None => now - chrono::Duration::from_std(DEFAULT_LOOKBACK).unwrap(), + }; + let until = match args.until.as_deref() { + Some(s) => parse_rfc3339(s)?, + None => now, + }; + if since > until { + return Err(IcLogsError::InvalidArg(format!( + "since {since} is after until {until}" + ))); + } + + let (ipv6, resolved_node_id) = self + .resolve_target(args.node_id.as_deref(), args.ipv6.as_deref()) + .await?; + + // gatewayd field-equality filter is encoded into the URL as + // `?_SYSTEMD_UNIT=foo.service`; the `Range: entries=:-N:N` + // header asks for the last N entries. + let url = format!( + "http://[{ipv6}]:{port}/entries?_SYSTEMD_UNIT={service}", + ipv6 = ipv6, + port = GATEWAYD_PORT, + service = args.service, + ); + let range = format!("entries=:-{lines}:{lines}"); + + let resp = self + .http + .get(&url) + .header(reqwest::header::ACCEPT, "application/json") + .header(reqwest::header::RANGE, range) + .send() + .await?; + let status = resp.status(); + if !status.is_success() { + let body = resp.text().await.unwrap_or_default(); + return Err(IcLogsError::Upstream { + status: status.as_u16(), + body: truncate(&body, 512), + }); + } + let body = resp.text().await?; + + // Most gatewayd builds emit one JSON object per line. Some + // newer builds use `application/json-seq` (RS-prefixed). We + // accept both by stripping leading `0x1e` (record separator) + // before parsing each line. + let mut entries: Vec = Vec::new(); + let mut total_seen: usize = 0; + for line in body.lines() { + let line = line.trim_start_matches('\u{1e}').trim(); + if line.is_empty() { + continue; + } + total_seen += 1; + let v: serde_json::Value = match serde_json::from_str(line) { + Ok(v) => v, + Err(_) => continue, + }; + let Some(entry) = decode_entry(&v) else { + continue; + }; + // Filter: time window. + let ts = match DateTime::parse_from_rfc3339(&entry.timestamp) { + Ok(t) => t.with_timezone(&Utc), + Err(_) => continue, + }; + if ts < since || ts > until { + continue; + } + // Filter: priority ceiling. + if entry.priority > priority { + continue; + } + // Filter: grep (literal substring, not regex). + if let Some(needle) = args.grep.as_deref() + && !needle.is_empty() + && !entry.message.contains(needle) + { + continue; + } + entries.push(entry); + } + + let truncated = total_seen as u32 >= lines && (entries.len() as u32) >= lines; + let returned = entries.len(); + + Ok(LogsOutput { + service: args.service, + returned, + truncated, + target: TargetInfo { + node_id: resolved_node_id, + ipv6: ipv6.to_string(), + url, + }, + entries, + }) + } +} + +fn truncate(s: &str, n: usize) -> String { + if s.len() <= n { + s.to_string() + } else { + format!("{}…", &s[..n]) + } +} diff --git a/rs/ai_agent/src/tools/ic_metrics.rs b/rs/ai_agent/src/tools/ic_metrics.rs new file mode 100644 index 000000000000..119d93daf19f --- /dev/null +++ b/rs/ai_agent/src/tools/ic_metrics.rs @@ -0,0 +1,649 @@ +//! Tool: `ic_metrics`. +//! +//! Scrapes Prometheus text exposition from one of the IC node's +//! observability endpoints (`replica`, `orchestrator`, `node_exporter`) +//! and answers four kinds of question about the result: +//! +//! * `list` — discover metric names without dumping a 10k-line scrape; +//! * `get` — current samples for a named metric, optionally filtered +//! by labels; +//! * `summary` — a curated dashboard per source so the LLM doesn't +//! have to know which 6 metric names matter for "is the replica +//! healthy?"; +//! * `rate` — per-second delta computed against a tiny in-memory LRU +//! of the previous (timestamp, value) for the same (source, metric, +//! labels) tuple. +//! +//! The target node's IPv6 is resolved through the shared +//! [`NodeDirectory`] from a `node_id` argument. As an escape hatch the +//! caller may pass a raw `ipv6` (e.g. when poking at a not-yet-in- +//! registry node from a dev machine). + +use std::{ + collections::BTreeMap, + net::Ipv6Addr, + str::FromStr, + sync::{Arc, Mutex}, + time::Duration, +}; + +use lru::LruCache; +use prometheus_parse::{Sample, Scrape, Value}; +use rig::{completion::ToolDefinition, tool::Tool}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use crate::state::AppState; + +/// Default port mapping for each known scrape source. Confirmed +/// against the GuestOS Prometheus job configs in +/// `dfinity/ic-observability-stack`. +const REPLICA_PORT: u16 = 9090; +const ORCHESTRATOR_PORT: u16 = 9091; +const NODE_EXPORTER_PORT: u16 = 9100; + +/// HTTP timeout for a single scrape. Generous because `node_exporter` +/// can take a few hundred ms to gather disk stats on a busy box. +const SCRAPE_TIMEOUT: Duration = Duration::from_secs(10); + +/// Cap on the rate-cache size. ~1k entries is roughly 100 metrics +/// across 10 nodes — enough headroom that we never thrash, but bounded +/// so a misbehaving LLM can't grow it without limit. +const RATE_CACHE_CAPACITY: usize = 1024; + +/// Cap on `op = "list"` results. The LLM should never need to see +/// more than this in one shot; if it does, it should narrow the +/// `metric` substring filter. +const LIST_LIMIT: usize = 200; + +#[derive(Debug, thiserror::Error)] +pub enum IcMetricsError { + #[error("invalid arg: {0}")] + InvalidArg(String), + + #[error("http error: {0}")] + Http(#[from] reqwest::Error), + + #[error("upstream {service} returned {status}: {body}")] + Upstream { + service: &'static str, + status: u16, + body: String, + }, + + #[error("prometheus parse error: {0}")] + Prom(String), + + #[error("node directory: {0}")] + Directory(#[from] crate::tools::node_directory::NodeDirectoryError), +} + +#[derive(Debug, Deserialize)] +pub struct MetricsArgs { + /// Source: "replica" | "orchestrator" | "node_exporter". + pub source: String, + + /// Operation: "list" | "get" | "summary" | "rate". + pub op: String, + + /// For "get"/"rate": metric name. For "list": optional substring + /// filter applied to metric names. + pub metric: Option, + + /// For "get"/"rate": optional label filter, e.g. + /// `{"status": "200"}`. All labels must match exactly. + pub labels: Option>, + + /// For "rate": window in seconds. Currently informational — the + /// rate is always computed against whatever previous sample we + /// have for the (source, metric, labels) tuple. Default 60. + pub window_secs: Option, + + /// Textual `NodeId` of the peer node to scrape. Resolved through + /// the registry local store. Either this OR `ipv6` must be + /// provided. + pub node_id: Option, + + /// Optional raw IPv6 override (without brackets). Useful for + /// dev/test before the target is in the registry. + pub ipv6: Option, +} + +#[derive(Debug, Serialize)] +pub struct MetricsOutput { + pub source: String, + pub op: String, + pub target: TargetInfo, + pub data: serde_json::Value, +} + +#[derive(Debug, Serialize)] +pub struct TargetInfo { + pub node_id: Option, + pub ipv6: String, + pub url: String, +} + +/// Source enum plus its scrape port and human label. +#[derive(Clone, Copy, Debug)] +enum Source { + Replica, + Orchestrator, + NodeExporter, +} + +impl Source { + fn parse(s: &str) -> Result { + match s { + "replica" => Ok(Self::Replica), + "orchestrator" => Ok(Self::Orchestrator), + "node_exporter" => Ok(Self::NodeExporter), + other => Err(IcMetricsError::InvalidArg(format!( + "unknown source '{other}'; expected replica|orchestrator|node_exporter" + ))), + } + } + + fn port(self) -> u16 { + match self { + Self::Replica => REPLICA_PORT, + Self::Orchestrator => ORCHESTRATOR_PORT, + Self::NodeExporter => NODE_EXPORTER_PORT, + } + } + + fn label(self) -> &'static str { + match self { + Self::Replica => "replica", + Self::Orchestrator => "orchestrator", + Self::NodeExporter => "node_exporter", + } + } +} + +/// Summary metric set per source (§4.3 of the spec). Stable order so +/// the LLM sees the same fields every time. +const REPLICA_SUMMARY: &[&str] = &[ + "replica_last_finalized_certified_height", + "mr_blocks_proposed_total", + "consensus_dkg_validator_time_seconds", + "state_manager_states_count", + "process_resident_memory_bytes", + "process_cpu_seconds_total", +]; + +const ORCHESTRATOR_SUMMARY: &[&str] = &[ + "orchestrator_dre_assigned_replica_version", + "orchestrator_replica_started_count", + "orchestrator_iteration_count_total", +]; + +const NODE_EXPORTER_SUMMARY: &[&str] = &[ + "node_load1", + "node_memory_MemAvailable_bytes", + "node_filesystem_avail_bytes", + "node_network_receive_bytes_total", + "node_disk_io_time_seconds_total", +]; + +/// Cache key for the rate computation. Labels are serialised +/// deterministically (BTreeMap iteration is ordered). +#[derive(Hash, Eq, PartialEq, Clone)] +struct RateKey { + source: &'static str, + target: String, + metric: String, + labels: String, +} + +/// Cached rate-cache entry. We store unix-seconds + the sample value +/// (cast to f64; histogram/counter buckets are read as f64 by +/// `prometheus_parse`). +#[derive(Clone, Copy)] +struct RateEntry { + timestamp_secs: i64, + value: f64, +} + +/// Tool struct. +pub struct IcMetrics { + state: Arc, + /// Shared HTTP client. `reqwest` clients are designed to be + /// long-lived and shared; building one per call would hammer the + /// connection pool and slow scrapes noticeably. + http: reqwest::Client, + /// Last sample for each (source, target, metric, labels) tuple. + /// `std::sync::Mutex` is fine here — the critical section is a + /// hashmap lookup + insert and never blocks on anything async. + rate_cache: Mutex>, +} + +impl IcMetrics { + pub fn new(state: Arc) -> Self { + let http = reqwest::Client::builder() + .timeout(SCRAPE_TIMEOUT) + // The ic-observability-stack scrape jobs talk plain HTTP; + // there's no certificate to validate. + .build() + .expect("reqwest client builder is infallible with default features"); + Self { + state, + http, + rate_cache: Mutex::new(LruCache::new(RATE_CACHE_CAPACITY)), + } + } + + /// Resolve the target's IPv6 from either `ipv6` (verbatim) or + /// `node_id` (registry lookup). + async fn resolve_target( + &self, + node_id: Option<&str>, + ipv6: Option<&str>, + ) -> Result<(Ipv6Addr, Option), IcMetricsError> { + if let Some(raw) = ipv6 { + let ip = Ipv6Addr::from_str(raw) + .map_err(|e| IcMetricsError::InvalidArg(format!("invalid ipv6 '{raw}': {e}")))?; + return Ok((ip, None)); + } + let nid = node_id.ok_or_else(|| { + IcMetricsError::InvalidArg("either node_id or ipv6 must be provided".to_string()) + })?; + let directory = self.state.node_directory().await?; + let ip = directory.resolve_ipv6(nid)?; + Ok((ip, Some(nid.to_string()))) + } + + /// Build the scrape URL for a given source + IPv6. + fn build_url(source: Source, ipv6: Ipv6Addr) -> String { + // Prometheus exposition is on `/metrics` by convention; the + // replica and orchestrator currently expose at `/` (which + // also serves an index page) — we go through `/metrics` for + // all three to be consistent. The IC observability scrape + // jobs use this path. + format!("http://[{}]:{}/metrics", ipv6, source.port()) + } + + /// Fetch and parse a scrape from the given URL. + async fn fetch_scrape(&self, source: Source, url: &str) -> Result { + let resp = self.http.get(url).send().await?; + let status = resp.status(); + if !status.is_success() { + let body = resp.text().await.unwrap_or_default(); + return Err(IcMetricsError::Upstream { + service: source.label(), + status: status.as_u16(), + body: truncate(&body, 512), + }); + } + let body = resp.text().await?; + // `prometheus_parse::Scrape::parse` wants an iterator of + // `Result` so it can stream over the lines. We + // already have the full body in memory. + let lines = body.lines().map(|l| Ok::<_, std::io::Error>(l.to_string())); + Scrape::parse(lines).map_err(|e| IcMetricsError::Prom(e.to_string())) + } + + fn op_list(scrape: &Scrape, filter: Option<&str>) -> serde_json::Value { + let mut names: Vec<&str> = scrape.docs.keys().map(|s| s.as_str()).collect(); + // Some metrics (e.g. process_*) appear only in `samples`, not + // `docs`. Union the two so the LLM sees everything. + for s in &scrape.samples { + names.push(&s.metric); + } + names.sort(); + names.dedup(); + + let filtered: Vec = match filter { + Some(f) if !f.is_empty() => { + let needle = f; + names + .iter() + .filter(|n| n.contains(needle)) + .map(|n| n.to_string()) + .collect() + } + _ => names.iter().map(|n| n.to_string()).collect(), + }; + + let total = filtered.len(); + let truncated = total > LIST_LIMIT; + let limited: Vec = filtered.into_iter().take(LIST_LIMIT).collect(); + json!({ + "metric_names": limited, + "total": total, + "truncated": truncated, + }) + } + + fn op_get( + scrape: &Scrape, + metric: &str, + labels: Option<&BTreeMap>, + ) -> serde_json::Value { + let samples = filter_samples(scrape, metric, labels); + let rendered: Vec = samples.iter().map(render_sample).collect(); + json!({ + "metric": metric, + "count": rendered.len(), + "samples": rendered, + }) + } + + fn op_summary(scrape: &Scrape, source: Source) -> serde_json::Value { + let names = match source { + Source::Replica => REPLICA_SUMMARY, + Source::Orchestrator => ORCHESTRATOR_SUMMARY, + Source::NodeExporter => NODE_EXPORTER_SUMMARY, + }; + let mut out = serde_json::Map::new(); + for name in names { + let samples = filter_samples(scrape, name, None); + let rendered: Vec = samples.iter().map(render_sample).collect(); + out.insert((*name).to_string(), json!(rendered)); + } + serde_json::Value::Object(out) + } + + fn op_rate( + &self, + scrape: &Scrape, + target: &str, + source: Source, + metric: &str, + labels: Option<&BTreeMap>, + window_secs: u64, + ) -> serde_json::Value { + let samples = filter_samples(scrape, metric, labels); + if samples.is_empty() { + return json!({ + "metric": metric, + "rate": null, + "value": null, + "hint": format!( + "no current sample for {metric} matching the requested labels" + ), + }); + } + + // `rate` is only meaningful on a single time series. If + // multiple samples come back, take the first and surface a + // hint so the LLM tightens its label filter. + let sample = &samples[0]; + let now = chrono::Utc::now().timestamp(); + let value = sample_to_f64(&sample.value); + + let key = RateKey { + source: source.label(), + target: target.to_string(), + metric: metric.to_string(), + labels: serialize_labels(labels), + }; + let prev = self.rate_cache.lock().unwrap().pop(&key); + self.rate_cache.lock().unwrap().put( + key, + RateEntry { + timestamp_secs: now, + value, + }, + ); + + let multi_hint = if samples.len() > 1 { + Some(format!( + "{} samples matched; rate computed from the first. Tighten labels for accuracy.", + samples.len() + )) + } else { + None + }; + + match prev { + Some(prev) => { + let dt = (now - prev.timestamp_secs) as f64; + if dt <= 0.0 { + return json!({ + "metric": metric, + "rate": null, + "value": value, + "hint": "previous sample was not older than the current; ignored", + }); + } + let rate = (value - prev.value) / dt; + let mut out = json!({ + "metric": metric, + "rate": rate, + "rate_per_sec": rate, + "value": value, + "previous_value": prev.value, + "delta_secs": dt, + "window_secs_requested": window_secs, + }); + if let Some(h) = multi_hint { + out["hint"] = serde_json::Value::String(h); + } + out + } + None => { + let mut out = json!({ + "metric": metric, + "rate": null, + "value": value, + "hint": "no prior sample; call rate again later to get a per-second delta", + }); + if let Some(h) = multi_hint { + out["secondary_hint"] = serde_json::Value::String(h); + } + out + } + } + } +} + +/// Filter `scrape.samples` to entries whose metric matches `name` and +/// whose labels are a superset of `required` (if any). +fn filter_samples<'a>( + scrape: &'a Scrape, + name: &str, + required: Option<&BTreeMap>, +) -> Vec<&'a Sample> { + scrape + .samples + .iter() + .filter(|s| s.metric == name) + .filter(|s| match required { + None => true, + Some(req) => req.iter().all(|(k, v)| s.labels.get(k) == Some(v.as_str())), + }) + .collect() +} + +/// Render a `prometheus_parse::Sample` to a small JSON object the LLM +/// can reason about without seeing parser internals. +fn render_sample(s: &&Sample) -> serde_json::Value { + // Sort labels deterministically for stable LLM-facing output — + // `prometheus_parse::Labels` is a HashMap underneath. + let labels: BTreeMap = s + .labels + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + json!({ + "labels": labels, + "value": sample_to_f64(&s.value), + "timestamp_unix_ms": s.timestamp.timestamp_millis(), + "kind": value_kind(&s.value), + }) +} + +/// Cast a `prometheus_parse::Value` to f64 for general comparisons. +/// Histograms and summaries are reduced to their `+Inf`-bucket count +/// (for histograms) or `count` (for summaries), which is the +/// monotonic component callers most often want a rate over. +fn sample_to_f64(v: &Value) -> f64 { + match v { + Value::Counter(x) | Value::Gauge(x) | Value::Untyped(x) => *x, + Value::Histogram(buckets) => { + // `+Inf` bucket = total count. + buckets.iter().last().map(|h| h.count).unwrap_or(0.0) + } + Value::Summary(quantiles) => { + // Summary `count` is not directly exposed by the parser + // here; fall back to the highest-quantile value as a + // monotone-ish proxy. + quantiles.iter().last().map(|q| q.count).unwrap_or(0.0) + } + } +} + +fn value_kind(v: &Value) -> &'static str { + match v { + Value::Counter(_) => "counter", + Value::Gauge(_) => "gauge", + Value::Untyped(_) => "untyped", + Value::Histogram(_) => "histogram", + Value::Summary(_) => "summary", + } +} + +fn serialize_labels(labels: Option<&BTreeMap>) -> String { + match labels { + None => String::new(), + Some(l) => { + let mut parts: Vec = l.iter().map(|(k, v)| format!("{k}={v}")).collect(); + parts.sort(); + parts.join(",") + } + } +} + +fn truncate(s: &str, n: usize) -> String { + if s.len() <= n { + s.to_string() + } else { + format!("{}…", &s[..n]) + } +} + +impl Tool for IcMetrics { + const NAME: &'static str = "ic_metrics"; + type Error = IcMetricsError; + type Args = MetricsArgs; + type Output = MetricsOutput; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: Self::NAME.to_string(), + description: "Fetch and analyze Prometheus metrics from one of three IC node sources: \ + `replica`, `orchestrator`, or `node_exporter` (guest VM). Operations: \ + `list` to discover metric names, `get` for current values, `summary` \ + for a curated set of operationally important metrics, `rate` for \ + per-second deltas. Use this for performance and resource questions. \ + Targets a specific peer node by `node_id` (resolved via the local \ + registry) or by raw `ipv6`." + .to_string(), + parameters: json!({ + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": ["replica", "orchestrator", "node_exporter"], + "description": "Which scrape endpoint to read." + }, + "op": { + "type": "string", + "enum": ["list", "get", "summary", "rate"], + "description": + "What to do with the scrape. list=metric names; \ + get=current samples; summary=curated dashboard; \ + rate=per-second delta vs. previous call." + }, + "metric": { + "type": "string", + "description": + "Metric name. Required for get/rate; optional substring \ + filter for list." + }, + "labels": { + "type": "object", + "additionalProperties": {"type": "string"}, + "description": + "Label exact-match filter, e.g. {\"status\":\"200\"}." + }, + "window_secs": { + "type": "integer", + "minimum": 1, + "description": "Hint for the rate window (default 60)." + }, + "node_id": { + "type": "string", + "description": + "Textual NodeId of the peer to scrape. Resolved through \ + the local registry. Either node_id or ipv6 is required." + }, + "ipv6": { + "type": "string", + "description": + "Raw IPv6 of the peer (without brackets). Override for \ + dev/test." + } + }, + "required": ["source", "op"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + let source = Source::parse(&args.source)?; + let (ipv6, resolved_node_id) = self + .resolve_target(args.node_id.as_deref(), args.ipv6.as_deref()) + .await?; + let url = Self::build_url(source, ipv6); + + // Validate per-op required args before doing the scrape so we + // fail fast on operator mistakes. + match args.op.as_str() { + "get" | "rate" => { + if args.metric.as_deref().unwrap_or("").is_empty() { + return Err(IcMetricsError::InvalidArg(format!( + "metric is required for op={}", + args.op + ))); + } + } + "list" | "summary" => {} + other => { + return Err(IcMetricsError::InvalidArg(format!( + "unknown op '{other}'; expected list|get|summary|rate" + ))); + } + } + + let scrape = self.fetch_scrape(source, &url).await?; + + let data = match args.op.as_str() { + "list" => Self::op_list(&scrape, args.metric.as_deref()), + "get" => Self::op_get(&scrape, args.metric.as_ref().unwrap(), args.labels.as_ref()), + "summary" => Self::op_summary(&scrape, source), + "rate" => self.op_rate( + &scrape, + &ipv6.to_string(), + source, + args.metric.as_ref().unwrap(), + args.labels.as_ref(), + args.window_secs.unwrap_or(60), + ), + // Already validated above. + _ => unreachable!(), + }; + + Ok(MetricsOutput { + source: source.label().to_string(), + op: args.op, + target: TargetInfo { + node_id: resolved_node_id, + ipv6: ipv6.to_string(), + url, + }, + data, + }) + } +} diff --git a/rs/ai_agent/src/tools/ic_state.rs b/rs/ai_agent/src/tools/ic_state.rs new file mode 100644 index 000000000000..cf48d93c25c4 --- /dev/null +++ b/rs/ai_agent/src/tools/ic_state.rs @@ -0,0 +1,536 @@ +//! Tool: `ic_state`. +//! +//! Read-only inspector of the replicated state that the AiNode's +//! state-sync replica persists to disk. We don't materialise a full +//! `ReplicatedState` (that would mean wiring up the entire state-manager +//! machinery on every tool call, including page allocators, prometheus +//! registries, and a thread pool, just to answer questions like "who +//! controls canister X?"). Instead we use `ic_state_layout` to point at +//! the on-disk checkpoint directory and decode the individual +//! `*.pbuf` files we care about — `system_metadata.pbuf` for the subnet +//! view, `canister.pbuf` for per-canister state. +//! +//! All paths are derived from `ic.json5` at tool-init time so the tool +//! tracks whatever state directory the orchestrator-managed replica +//! actually writes to. + +use std::{ + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, +}; + +use ic_base_types::{CanisterId, PrincipalId}; +use ic_config::{Config, ConfigSource}; +use ic_protobuf::state::{ + canister_state_bits::v1::CanisterStateBits, system_metadata::v1::SystemMetadata, +}; +use ic_state_layout::{CheckpointLayout, ReadOnly, error::LayoutError}; +use ic_types::Height; +use rig::{completion::ToolDefinition, tool::Tool}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use crate::{state::AppState, tools::node_directory::NodeDirectory}; + +/// Subdirectory of `state_root` that holds the verified checkpoints. +/// Mirrors `ic_state_layout::CHECKPOINTS_DIR`. +const CHECKPOINTS_DIR: &str = "checkpoints"; + +#[derive(Debug, thiserror::Error)] +pub enum IcStateError { + #[error("io error reading {path}: {source}")] + Io { + path: PathBuf, + #[source] + source: std::io::Error, + }, + + #[error("state layout error: {0}")] + Layout(#[from] LayoutError), + + #[error("invalid arg: {0}")] + InvalidArg(String), + + #[error("not found: {0}")] + NotFound(String), + + #[error("failed to load replica config from {path}: {message}")] + Config { path: PathBuf, message: String }, +} + +#[derive(Debug, Deserialize)] +pub struct IcStateArgs { + /// Operation to run. One of: + /// - `list_checkpoints` — heights of every verified or state-sync + /// checkpoint currently on disk, ordered ascending. + /// - `subnet` — own-subnet metadata from the latest checkpoint: + /// subnet id, subnet type, node membership, NNS subnet id, batch + /// time, canister id ranges allocated to this subnet. + /// - `list_canisters` — canister ids present in the latest + /// checkpoint, sorted, optionally filtered by substring. + /// - `canister` — full per-canister view (controllers, cycles + /// balance, module hash, freeze threshold, memory/compute + /// allocations, version). + pub op: String, + + /// Specific checkpoint height. Default: the latest checkpoint. + /// Heights are u64; pass as a JSON number. + pub height: Option, + + /// Substring filter for `list_canisters` (matched against the + /// textual canister id, case-insensitive). + pub filter: Option, + + /// Canister id (textual form, e.g. `rrkah-fqaaa-aaaaa-aaaaq-cai`) + /// for `op = "canister"`. + pub canister_id: Option, +} + +#[derive(Debug, Serialize)] +pub struct IcStateOutput { + pub op: String, + pub state_root: String, + pub data: serde_json::Value, +} + +/// Tool struct. Holds the resolved state root path so we don't re-parse +/// `ic.json5` on every call. Re-parsing on every call would also race +/// against the orchestrator if/when it rewrites the file. +pub struct IcState { + state_root: PathBuf, + /// Kept for diagnostics in error messages, for the `state_root` + /// field of every response (so the LLM can confirm which directory + /// it's looking at), and to lazily fetch the shared node + /// directory in `op=subnet`. + state: Arc, +} + +impl IcState { + /// Construct the tool. Parses `ic.json5` to discover the on-disk + /// state root. + /// + /// Async to mirror the spec's `IcState::new(state.clone()).await?` + /// shape (parsing is in fact synchronous; we shell out to + /// `tokio::task::spawn_blocking` to avoid stalling the executor on + /// the file read). + pub async fn new(state: Arc) -> Result { + let cfg_path = state.config.ic_config_path.clone(); + let state_root = tokio::task::spawn_blocking(move || resolve_state_root(&cfg_path)) + .await + .map_err(|e| IcStateError::Config { + path: state.config.ic_config_path.clone(), + message: format!("join error: {e}"), + })??; + Ok(Self { state_root, state }) + } + + /// Convenience constructor used in unit tests where we already know + /// the state root. + #[cfg(test)] + pub fn for_root(state: Arc, state_root: PathBuf) -> Self { + Self { state_root, state } + } + + /// Return the available verified checkpoint heights, ascending. + fn list_checkpoint_heights(&self) -> Result, IcStateError> { + let cps_dir = self.state_root.join(CHECKPOINTS_DIR); + let entries = std::fs::read_dir(&cps_dir).map_err(|e| IcStateError::Io { + path: cps_dir.clone(), + source: e, + })?; + let mut heights = Vec::new(); + for entry in entries { + let entry = entry.map_err(|e| IcStateError::Io { + path: cps_dir.clone(), + source: e, + })?; + if !entry.file_type().map(|t| t.is_dir()).unwrap_or(false) { + continue; + } + // Checkpoint dirs are named with a 16-char zero-padded hex + // height, e.g. "0000000000001234". `state_layout` is the + // source of truth for the format + // (`StateLayout::checkpoint_name`). Skip anything that + // doesn't parse — the directory may also contain markers + // for unverified or in-progress checkpoints in older + // layouts. + let name = entry.file_name(); + let name = name.to_string_lossy(); + if let Ok(h) = u64::from_str_radix(&name, 16) { + heights.push(h); + } + } + heights.sort_unstable(); + Ok(heights) + } + + /// Pick a checkpoint height: caller-supplied if present, else the + /// latest available. + fn resolve_height(&self, requested: Option) -> Result { + let heights = self.list_checkpoint_heights()?; + if heights.is_empty() { + return Err(IcStateError::NotFound(format!( + "no checkpoints under {}", + self.state_root.display() + ))); + } + match requested { + None => Ok(*heights.last().unwrap()), + Some(h) => { + if heights.contains(&h) { + Ok(h) + } else { + Err(IcStateError::NotFound(format!( + "checkpoint at height {h} not found; available: {heights:?}" + ))) + } + } + } + } + + fn checkpoint_layout(&self, height: u64) -> Result, IcStateError> { + let name = format!("{:016x}", height); + let cp_root = self.state_root.join(CHECKPOINTS_DIR).join(name); + Ok(CheckpointLayout::::new_untracked( + cp_root, + Height::new(height), + )?) + } + + async fn op_subnet(&self, height: u64) -> Result { + let cp = self.checkpoint_layout(height)?; + let metadata: SystemMetadata = cp.system_metadata().deserialize()?; + + let own_subnet_id = metadata + .own_subnet_id + .as_ref() + .and_then(principal_to_string); + + let nns_subnet_id = metadata + .network_topology + .as_ref() + .and_then(|nt| nt.nns_subnet_id.as_ref()) + .and_then(principal_to_string); + + // Best-effort: try to resolve node ipv6 addresses through the + // shared registry-backed directory. If the registry isn't + // reachable yet (e.g. fresh AiNode that hasn't synced) we + // simply omit the ipv6 field on each entry — the LLM will + // still see the node ids. + let directory: Option> = self.state.node_directory().await.ok(); + + // Membership of every subnet (including this one) plus subnet + // type. Useful for "how many nodes in subnet Z?" questions. + let mut subnets = Vec::new(); + if let Some(nt) = &metadata.network_topology { + for entry in &nt.subnets { + let sid = entry.subnet_id.as_ref().and_then(principal_to_string); + let topology = entry.subnet_topology.as_ref(); + let node_ids: Vec = topology + .map(|t| { + t.nodes + .iter() + .filter_map(|n| n.node_id.as_ref().and_then(principal_to_string)) + .collect() + }) + .unwrap_or_default(); + let subnet_type = topology.map(|t| t.subnet_type).unwrap_or(0); + + let nodes: Vec = node_ids + .iter() + .map(|nid| { + let ipv6 = directory + .as_ref() + .and_then(|d| d.resolve_ipv6(nid).ok()) + .map(|ip| ip.to_string()); + json!({"node_id": nid, "ipv6": ipv6}) + }) + .collect(); + + subnets.push(json!({ + "subnet_id": sid, + "node_count": nodes.len(), + "nodes": nodes, + "subnet_type": subnet_type, + })); + } + } + + // Canister id ranges allocated to this subnet (so the LLM can + // answer "is canister X on this subnet?"). + let canister_ranges = metadata.canister_allocation_ranges.as_ref().map(|r| { + r.ranges + .iter() + .filter_map(|range| { + let start = range + .start_canister_id + .as_ref() + .and_then(principal_to_string); + let end = range.end_canister_id.as_ref().and_then(principal_to_string); + Some(json!({"start": start?, "end": end?})) + }) + .collect::>() + }); + + Ok(json!({ + "height": height, + "own_subnet_id": own_subnet_id, + "nns_subnet_id": nns_subnet_id, + "batch_time_nanos": metadata.batch_time_nanos, + "state_sync_version": metadata.state_sync_version, + "certification_version": metadata.certification_version, + "subnets": subnets, + "canister_allocation_ranges": canister_ranges, + })) + } + + fn op_list_canisters( + &self, + height: u64, + filter: Option<&str>, + ) -> Result { + let cp = self.checkpoint_layout(height)?; + let mut ids: Vec = cp.canister_ids()?; + ids.sort(); + let ids_str: Vec = ids.into_iter().map(|c| c.to_string()).collect(); + let filtered: Vec = match filter { + Some(f) if !f.is_empty() => { + let needle = f.to_lowercase(); + ids_str + .into_iter() + .filter(|id| id.to_lowercase().contains(&needle)) + .collect() + } + _ => ids_str, + }; + Ok(json!({ + "height": height, + "count": filtered.len(), + "canister_ids": filtered, + })) + } + + fn op_canister( + &self, + height: u64, + canister_id: &str, + ) -> Result { + let cid = CanisterId::from_str(canister_id).map_err(|e| { + IcStateError::InvalidArg(format!("invalid canister_id '{canister_id}': {e}")) + })?; + let cp = self.checkpoint_layout(height)?; + let canister_layout = cp.canister(&cid)?; + if !canister_layout.raw_path().exists() { + return Err(IcStateError::NotFound(format!( + "canister {canister_id} not found at height {height}" + ))); + } + let bits: CanisterStateBits = canister_layout.canister().deserialize()?; + let cycles_balance = bits + .cycles_balance + .as_ref() + .map(|c| u128_from_le_bytes(&c.raw_cycles)); + let reserved_balance = bits + .reserved_balance + .as_ref() + .map(|c| u128_from_le_bytes(&c.raw_cycles)); + let controllers: Vec = bits + .controllers + .iter() + .filter_map(principal_to_string) + .collect(); + let module_hash_hex = bits + .execution_state_bits + .as_ref() + .map(|esb| hex::encode(&esb.binary_hash)) + .filter(|h| !h.is_empty()); + let wasm64 = bits.execution_state_bits.as_ref().map(|esb| esb.is_wasm64); + + Ok(json!({ + "height": height, + "canister_id": canister_id, + "controllers": controllers, + "cycles_balance": cycles_balance.map(|v| v.to_string()), + "reserved_balance": reserved_balance.map(|v| v.to_string()), + "freeze_threshold": bits.freeze_threshold, + "compute_allocation": bits.compute_allocation, + "memory_allocation": bits.memory_allocation, + "canister_version": bits.canister_version, + "stable_memory_size_bytes": bits.stable_memory_size64, + "module_hash": module_hash_hex, + "is_wasm64": wasm64, + })) + } +} + +/// Best-effort conversion from a protobuf `PrincipalId` (any of the IC's +/// `PrincipalId`-shaped messages share the same `raw` field) to its +/// textual form. +fn principal_to_string(p: &P) -> Option { + PrincipalId::try_from(p.raw_bytes()) + .ok() + .map(|p| p.to_string()) +} + +/// Tiny abstraction over the various protobuf PrincipalId-shaped +/// messages so we can write `principal_to_string` once. The proto +/// modules don't share a common trait so we adapt them locally. +trait HasRaw { + fn raw_bytes(&self) -> &[u8]; +} + +impl HasRaw for ic_protobuf::types::v1::PrincipalId { + fn raw_bytes(&self) -> &[u8] { + &self.raw + } +} + +impl HasRaw for ic_protobuf::types::v1::SubnetId { + fn raw_bytes(&self) -> &[u8] { + // SubnetId wraps a PrincipalId. + self.principal_id + .as_ref() + .map(|p| p.raw.as_slice()) + .unwrap_or(&[]) + } +} + +impl HasRaw for ic_protobuf::types::v1::NodeId { + fn raw_bytes(&self) -> &[u8] { + self.principal_id + .as_ref() + .map(|p| p.raw.as_slice()) + .unwrap_or(&[]) + } +} + +impl HasRaw for ic_protobuf::types::v1::CanisterId { + fn raw_bytes(&self) -> &[u8] { + self.principal_id + .as_ref() + .map(|p| p.raw.as_slice()) + .unwrap_or(&[]) + } +} + +/// Decode a little-endian byte sequence (up to 16 bytes) into a u128. +/// `Cycles.raw_cycles` is the LE encoding of the underlying u128. +fn u128_from_le_bytes(bytes: &[u8]) -> u128 { + let mut buf = [0u8; 16]; + let n = bytes.len().min(16); + buf[..n].copy_from_slice(&bytes[..n]); + u128::from_le_bytes(buf) +} + +/// Parse `ic.json5` and return the absolute state root. +/// +/// `Config::load_with_tmpdir` needs a tmpdir to materialise some +/// derived paths internally; we don't keep it alive afterwards because +/// we only read out `state_manager.state_root()` which doesn't escape +/// it. +fn resolve_state_root(cfg_path: &Path) -> Result { + if !cfg_path.exists() { + return Err(IcStateError::Config { + path: cfg_path.to_path_buf(), + message: "file not found".to_string(), + }); + } + let tmpdir = tempfile::Builder::new() + .prefix("ic-ai-agent-cfg") + .tempdir() + .map_err(|e| IcStateError::Config { + path: cfg_path.to_path_buf(), + message: format!("failed to create tmpdir: {e}"), + })?; + let config = Config::load_with_tmpdir( + ConfigSource::File(cfg_path.to_path_buf()), + tmpdir.path().to_path_buf(), + ); + Ok(config.state_manager.state_root()) +} + +impl Tool for IcState { + const NAME: &'static str = "ic_state"; + type Error = IcStateError; + type Args = IcStateArgs; + type Output = IcStateOutput; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: Self::NAME.to_string(), + description: "Query the Internet Computer state for canister, subnet, or node \ + information. Use this when the operator asks about what's deployed, \ + who controls a canister, cycles balance, module hash, or node \ + membership. Does not return metrics or logs — use `ic_metrics` or \ + `ic_logs` for those." + .to_string(), + parameters: json!({ + "type": "object", + "properties": { + "op": { + "type": "string", + "enum": ["list_checkpoints", "subnet", "list_canisters", "canister"], + "description": + "Which view of state to return. \ + list_checkpoints: heights on disk. \ + subnet: own-subnet topology + canister id ranges. \ + list_canisters: canister ids in the latest (or specified) checkpoint. \ + canister: full per-canister state." + }, + "height": { + "type": "integer", + "minimum": 0, + "description": "Optional checkpoint height. Defaults to the latest." + }, + "filter": { + "type": "string", + "description": "Substring filter for list_canisters (case-insensitive)." + }, + "canister_id": { + "type": "string", + "description": + "Required for op=canister. Textual canister id, \ + e.g. 'rrkah-fqaaa-aaaaa-aaaaq-cai'." + } + }, + "required": ["op"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + let data = match args.op.as_str() { + "list_checkpoints" => { + let heights = self.list_checkpoint_heights()?; + let count = heights.len(); + json!({"heights": heights, "count": count}) + } + "subnet" => { + let h = self.resolve_height(args.height)?; + self.op_subnet(h).await? + } + "list_canisters" => { + let h = self.resolve_height(args.height)?; + self.op_list_canisters(h, args.filter.as_deref())? + } + "canister" => { + let cid = args.canister_id.ok_or_else(|| { + IcStateError::InvalidArg("canister_id is required for op=canister".to_string()) + })?; + let h = self.resolve_height(args.height)?; + self.op_canister(h, &cid)? + } + other => { + return Err(IcStateError::InvalidArg(format!( + "unknown op '{other}'; expected list_checkpoints|subnet|list_canisters|canister" + ))); + } + }; + Ok(IcStateOutput { + op: args.op, + state_root: self.state_root.display().to_string(), + data, + }) + } +} diff --git a/rs/ai_agent/src/tools/mod.rs b/rs/ai_agent/src/tools/mod.rs index dc32f0265175..5503fa81a768 100644 --- a/rs/ai_agent/src/tools/mod.rs +++ b/rs/ai_agent/src/tools/mod.rs @@ -8,8 +8,15 @@ pub mod calculator; pub mod current_datetime; +pub mod ic_logs; +pub mod ic_metrics; +pub mod ic_state; +pub mod node_directory; pub mod registry; pub use calculator::Calculator; pub use current_datetime::CurrentDateTime; +pub use ic_logs::IcLogs; +pub use ic_metrics::IcMetrics; +pub use ic_state::IcState; pub use registry::{registered_tool_names, validate_tool_names}; diff --git a/rs/ai_agent/src/tools/node_directory.rs b/rs/ai_agent/src/tools/node_directory.rs new file mode 100644 index 000000000000..dd9d5c9c2cf5 --- /dev/null +++ b/rs/ai_agent/src/tools/node_directory.rs @@ -0,0 +1,137 @@ +//! Shared helper: resolve `NodeId -> Ipv6Addr` from the local registry +//! store. +//! +//! Both `ic_metrics` and `ic_logs` need to talk to a peer node in the +//! subnet that this AiNode is shadowing. The LLM passes a textual node +//! id (which it discovered via `ic_state`); we look it up in the +//! registry the orchestrator-managed state-sync replica keeps on disk +//! and return the IPv6 published in the node's `NodeRecord.http`. +//! +//! Single instance per process. Holds a `RegistryClientImpl` backed by +//! a `LocalStoreImpl`. We never spawn the background polling thread — +//! the AiNode's orchestrator is the only writer, and the local store is +//! cheap to re-poll on every lookup. + +use std::{ + net::Ipv6Addr, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, +}; + +use ic_config::{Config, ConfigSource}; +use ic_interfaces_registry::RegistryClient; +use ic_registry_client::client::RegistryClientImpl; +use ic_registry_client_helpers::node::NodeRegistry; +use ic_registry_local_store::LocalStoreImpl; +use ic_types::{NodeId, PrincipalId}; + +#[derive(Debug, thiserror::Error)] +pub enum NodeDirectoryError { + #[error("failed to load replica config from {path}: {message}")] + Config { path: PathBuf, message: String }, + + #[error("registry local store not configured in {path}")] + NoLocalStore { path: PathBuf }, + + #[error("registry client error: {0:?}")] + Registry(ic_types::registry::RegistryClientError), + + #[error("invalid node id '{0}': {1}")] + InvalidNodeId(String, String), + + #[error("node {0} not found in registry at version {1}")] + NodeNotFound(NodeId, u64), + + #[error("node {0} has no http endpoint in its NodeRecord")] + NoHttpEndpoint(NodeId), + + #[error("node {0} http endpoint ip '{1}' is not a valid IPv6 address: {2}")] + BadIpv6(NodeId, String, std::net::AddrParseError), +} + +impl From for NodeDirectoryError { + fn from(e: ic_types::registry::RegistryClientError) -> Self { + NodeDirectoryError::Registry(e) + } +} + +/// Wraps a `RegistryClientImpl` driven by a local store on disk. +pub struct NodeDirectory { + client: Arc, +} + +impl NodeDirectory { + /// Build a directory backed by the registry local store referenced + /// by the given replica config. + pub fn from_ic_config(ic_config_path: &Path) -> Result { + let local_store_path = local_store_from_ic_config(ic_config_path)?; + let data_provider: Arc = + Arc::new(LocalStoreImpl::new(local_store_path)); + let client = Arc::new(RegistryClientImpl::new(data_provider, None)); + // One synchronous poll so the cache is non-empty for the very + // first lookup. Failures here are non-fatal: subsequent + // `lookup` calls will re-poll and may succeed once the + // orchestrator has populated the store. + let _ = client.poll_once(); + Ok(Self { client }) + } + + /// Resolve a textual `NodeId` to its IPv6 address. + /// + /// Re-polls the registry on every call so we pick up new nodes + /// without having to restart the agent. + pub fn resolve_ipv6(&self, node_id_str: &str) -> Result { + let principal = PrincipalId::from_str(node_id_str).map_err(|e| { + NodeDirectoryError::InvalidNodeId(node_id_str.to_string(), e.to_string()) + })?; + let node_id = NodeId::from(principal); + + // Best-effort refresh; ignore transient failures and use + // whatever's cached. + let _ = self.client.poll_once(); + + let version = self.client.get_latest_version(); + let record = self + .client + .get_node_record(node_id, version)? + .ok_or(NodeDirectoryError::NodeNotFound(node_id, version.get()))?; + let http = record + .http + .ok_or(NodeDirectoryError::NoHttpEndpoint(node_id))?; + let ip = http.ip_addr; + Ipv6Addr::from_str(&ip).map_err(|e| NodeDirectoryError::BadIpv6(node_id, ip, e)) + } +} + +/// Parse `ic.json5` and return the registry local store path. +/// +/// `Config::load_with_tmpdir` materialises some derived paths but the +/// `registry_client.local_store` field is taken verbatim from the +/// config file. +fn local_store_from_ic_config(cfg_path: &Path) -> Result { + if !cfg_path.exists() { + return Err(NodeDirectoryError::Config { + path: cfg_path.to_path_buf(), + message: "file not found".to_string(), + }); + } + let tmpdir = tempfile::Builder::new() + .prefix("ic-ai-agent-cfg") + .tempdir() + .map_err(|e| NodeDirectoryError::Config { + path: cfg_path.to_path_buf(), + message: format!("failed to create tmpdir: {e}"), + })?; + let config = Config::load_with_tmpdir( + ConfigSource::File(cfg_path.to_path_buf()), + tmpdir.path().to_path_buf(), + ); + let path = config.registry_client.local_store; + if path.as_os_str().is_empty() { + return Err(NodeDirectoryError::NoLocalStore { + path: cfg_path.to_path_buf(), + }); + } + Ok(path) +} diff --git a/rs/ai_agent/src/tools/registry.rs b/rs/ai_agent/src/tools/registry.rs index f70858e5eb35..b098ff481504 100644 --- a/rs/ai_agent/src/tools/registry.rs +++ b/rs/ai_agent/src/tools/registry.rs @@ -5,11 +5,17 @@ use rig::tool::Tool; -use super::{Calculator, CurrentDateTime}; +use super::{Calculator, CurrentDateTime, IcLogs, IcMetrics, IcState}; /// Returns the names of all built-in tools. pub fn registered_tool_names() -> &'static [&'static str] { - &[Calculator::NAME, CurrentDateTime::NAME] + &[ + Calculator::NAME, + CurrentDateTime::NAME, + IcState::NAME, + IcMetrics::NAME, + IcLogs::NAME, + ] } /// Validates that every name in `requested` exists in the registry. From a3cddbe513a9d7bb7535c79a2991f160bcca510b Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 4 May 2026 23:08:23 +0000 Subject: [PATCH 04/11] fixing quic --- .../guestos/ai-agent/ic-ai-agent.service | 24 +++++++++++++++---- ic-os/guestos/context/Dockerfile | 20 ++++++++++++++++ .../quic_transport/src/connection_manager.rs | 16 +++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/ic-os/components/guestos/ai-agent/ic-ai-agent.service b/ic-os/components/guestos/ai-agent/ic-ai-agent.service index 4232ea70a609..69caed6ab670 100644 --- a/ic-os/components/guestos/ai-agent/ic-ai-agent.service +++ b/ic-os/components/guestos/ai-agent/ic-ai-agent.service @@ -12,7 +12,19 @@ Wants=network-online.target [Service] Type=simple -DynamicUser=yes + +# Run as the dedicated `ic-ai-agent` system user. That user is set up in +# the GuestOS Dockerfile and joined to: +# +# * `nonconfidential` — read access to /var/lib/ic/data/ic_state +# * `ic-registry-local-store` — read access to the local registry store +# +# We deliberately do NOT use DynamicUser=yes here: the IC observability +# tools (`ic_state`, `ic_metrics`, `ic_logs`) need to read directories +# owned by `ic-replica:nonconfidential`, which a dynamic UID (with no +# static group memberships) cannot do. +User=ic-ai-agent +Group=ic-ai-agent # Bind to loopback only on a non-privileged port. External clients reach # the service through the stunnel TLS terminator on :::11500 (see @@ -25,9 +37,13 @@ ExecStart=/opt/ic/bin/ic-ai-agent --addr 127.0.0.1:11501 Restart=on-failure RestartSec=5s -# The agent is a stateless HTTP server: no need for state directories. -# DynamicUser provides automatic /tmp isolation; no extra hardening needed -# beyond the dm-verity-protected rootfs. +# The agent is a stateless HTTP server: no on-disk state of its own. +# Light hardening that doesn't conflict with reading the replica's +# state/registry trees. +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=yes +NoNewPrivileges=yes [Install] WantedBy=multi-user.target diff --git a/ic-os/guestos/context/Dockerfile b/ic-os/guestos/context/Dockerfile index c261fb6685cf..bf5a7c1f523f 100644 --- a/ic-os/guestos/context/Dockerfile +++ b/ic-os/guestos/context/Dockerfile @@ -257,6 +257,26 @@ RUN addgroup --system ollama && \ chown ollama:ollama /var/lib/ollama && \ chmod 0750 /var/lib/ollama +# The "ic-ai-agent" account. Used to run the `ic-ai-agent` HTTP service +# (started on AI nodes by manage-ai-agent.sh, driven by the +# orchestrator's AiNodeManager). +# +# The agent's IC-observability tools need read access to two on-disk +# trees written by the state-sync replica: +# +# * /var/lib/ic/data/ic_state (group nonconfidential) +# * /var/lib/ic/data/ic_registry_local_store +# (group ic-registry-local-store) +# +# Both are set up by setup-permissions.sh as group-readable. We add +# `ic-ai-agent` to those groups here so the unix permission check passes; +# without this the service runs under DynamicUser/an isolated UID and +# gets EACCES walking the checkpoints directory. +RUN addgroup --system ic-ai-agent && \ + adduser --system --disabled-password --shell /usr/sbin/nologin --no-create-home --ingroup ic-ai-agent -c "IC AI Agent" ic-ai-agent && \ + adduser ic-ai-agent nonconfidential && \ + adduser ic-ai-agent ic-registry-local-store + # ------ INSTALL SCRIPTS ----------------------------------------- # Install IC binaries and other data late -- this means everything above diff --git a/rs/p2p/quic_transport/src/connection_manager.rs b/rs/p2p/quic_transport/src/connection_manager.rs index c55136fd8203..9806e1394de8 100644 --- a/rs/p2p/quic_transport/src/connection_manager.rs +++ b/rs/p2p/quic_transport/src/connection_manager.rs @@ -553,6 +553,13 @@ impl ConnectionManager { fn handle_inbound_conn_attemp(&mut self, incoming: Incoming) { self.metrics.inbound_connection_total.inc(); let node_id = self.node_id; + // AI-node peers always dial members, regardless of NodeId ordering + // (see `can_i_dial_to`). So when a member receives an inbound + // connection, the standard "lower id is dialer" rule must be + // bypassed if the connecting peer is an AI peer in our current + // topology. Snapshot the AI-peer set here so the validation + // future doesn't need to reach back into `&self.topology`. + let ai_peers = self.topology.get_ai_peers(); let conn_fut = async move { let established = incoming @@ -578,8 +585,13 @@ impl ConnectionManager { let peer_id = node_id_from_certificate_der(rustls_cert.as_ref()) .map_err(|err| ConnectionEstablishError::AuthenticationFailed(err.to_string()))?; - // Lower ID is dialer. So we reject if this nodes id is higher. - if peer_id > node_id { + // Standard rule: lower NodeId is dialer, higher is acceptor. + // Exception: AI-node peers always dial regardless of ordering + // because they never accept inbound from us. Without this + // bypass an AI peer with a NodeId greater than ours would + // dial us, get rejected here as `InvalidIncomingPeerId`, and + // never make progress on state-sync. + if peer_id > node_id && !ai_peers.contains(&peer_id) { return Err(ConnectionEstablishError::InvalidIncomingPeerId { client: peer_id, server: node_id, From 1194c7bf02dfce521b63b9c4f8dc136fb6d3cca9 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Tue, 5 May 2026 23:19:40 +0000 Subject: [PATCH 05/11] fixing metrics tool --- rs/ai_agent/src/tools/ic_metrics.rs | 174 ++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 38 deletions(-) diff --git a/rs/ai_agent/src/tools/ic_metrics.rs b/rs/ai_agent/src/tools/ic_metrics.rs index 119d93daf19f..4d39278b398a 100644 --- a/rs/ai_agent/src/tools/ic_metrics.rs +++ b/rs/ai_agent/src/tools/ic_metrics.rs @@ -35,9 +35,19 @@ use serde_json::json; use crate::state::AppState; -/// Default port mapping for each known scrape source. Confirmed -/// against the GuestOS Prometheus job configs in -/// `dfinity/ic-observability-stack`. +// Per-source URL parameters. Each scrape source on a GuestOS node +// exposes Prometheus exposition with a slightly different shape — +// these constants are the source of truth, verified against live +// nodes: +// +// replica → http://[ipv6]:9090/ (root, plain HTTP) +// orchestrator → http://[ipv6]:9091/ (root, plain HTTP) +// node_exporter → https://[ipv6]:9100/metrics +// (self-signed TLS, /metrics path) +// +// The HTTPS-with-self-signed-cert quirk on node_exporter means we have +// to disable certificate validation on the shared HTTP client; see +// `IcMetrics::new` below. const REPLICA_PORT: u16 = 9090; const ORCHESTRATOR_PORT: u16 = 9091; const NODE_EXPORTER_PORT: u16 = 9100; @@ -159,31 +169,89 @@ impl Source { Self::NodeExporter => "node_exporter", } } -} -/// Summary metric set per source (§4.3 of the spec). Stable order so -/// the LLM sees the same fields every time. -const REPLICA_SUMMARY: &[&str] = &[ - "replica_last_finalized_certified_height", - "mr_blocks_proposed_total", - "consensus_dkg_validator_time_seconds", - "state_manager_states_count", - "process_resident_memory_bytes", - "process_cpu_seconds_total", -]; + /// URL scheme this source listens on. node_exporter terminates TLS + /// (with a self-signed cert); the replica and orchestrator are + /// plain HTTP on a loopback-style admin port. + fn scheme(self) -> &'static str { + match self { + Self::Replica | Self::Orchestrator => "http", + Self::NodeExporter => "https", + } + } -const ORCHESTRATOR_SUMMARY: &[&str] = &[ - "orchestrator_dre_assigned_replica_version", - "orchestrator_replica_started_count", - "orchestrator_iteration_count_total", -]; + /// URL path. The replica and orchestrator serve their full + /// exposition at `/`; node_exporter at `/metrics` per upstream + /// convention. + fn path(self) -> &'static str { + match self { + Self::Replica | Self::Orchestrator => "/", + Self::NodeExporter => "/metrics", + } + } +} +// Curated `summary` metric sets per source. Every name below has been +// verified against a real GuestOS node's exposition output — *not* +// cross-referenced from upstream docs, where naming drifts. Order is +// intentional: most-actionable first, so an LLM that only quotes the +// first few entries still surfaces the most useful signal. +// +// When the IC code base renames a metric, update these lists in lockstep +// — `op = "summary"` will silently return zero samples for a stale name +// rather than erroring. + +/// node_exporter (host VM) — primary triage source. Covers CPU, memory, +/// swap, disk space, disk I/O saturation, network throughput, PSI +/// (pressure stall), clock health, file-descriptor exhaustion, and live +/// TCP socket count. Filesystem and disk metrics are per-mount/per-device, +/// so the LLM should narrow with `op = "get"` + label filter (e.g. +/// `mountpoint="/var/lib/ic/data"` for the IC state partition) to make +/// sense of multi-sample results. const NODE_EXPORTER_SUMMARY: &[&str] = &[ "node_load1", + "node_load5", + "node_load15", "node_memory_MemAvailable_bytes", + "node_memory_MemTotal_bytes", + "node_memory_SwapFree_bytes", + "node_memory_SwapTotal_bytes", "node_filesystem_avail_bytes", - "node_network_receive_bytes_total", + "node_filesystem_size_bytes", "node_disk_io_time_seconds_total", + "node_network_receive_bytes_total", + "node_network_transmit_bytes_total", + "node_pressure_io_stalled_seconds_total", + "node_time_seconds", + "node_filefd_allocated", + "node_filefd_maximum", + "node_netstat_Tcp_CurrEstab", +]; + +/// replica (consensus + state-sync internals). Health-focused: certified +/// height, checkpoint count, batch height, block production, peer-count +/// sanity, critical-error counter, RSS. Heavier debugging paths (QUIC, +/// state-sync detail) live behind `op = "get"`. +const REPLICA_SUMMARY: &[&str] = &[ + "state_manager_latest_certified_height", + "state_manager_checkpoints_on_disk_count", + "consensus_batch_height", + "mr_blocks_proposed_total", + "mr_subnet_size", + "critical_errors", + "process_resident_memory_bytes", +]; + +/// orchestrator (replica supervisor + upgrade manager). Small surface; +/// most names are zero in steady state and only move when something is +/// going wrong, which is exactly what we want to surface in a summary. +const ORCHESTRATOR_SUMMARY: &[&str] = &[ + "orchestrator_cup_deserialization_failed_total", + "orchestrator_failed_consecutive_upgrade_checks_total", + "orchestrator_replica_process_start_attempts_total", + "orchestrator_state_removal_failed_total", + "orchestrator_key_rotation_status", + "reboot_duration_seconds", ]; /// Cache key for the rate computation. Labels are serialised @@ -220,10 +288,17 @@ pub struct IcMetrics { impl IcMetrics { pub fn new(state: Arc) -> Self { + // node_exporter terminates TLS with a self-signed certificate + // (per GuestOS provisioning). Replica and orchestrator both + // serve plain HTTP, so the only cost of disabling cert + // validation here is on the node_exporter path. We rely on + // (a) reaching the node over an IPv6 address that came from + // the registry and (b) the node_exporter port being firewalled + // to the IC peer mesh — both of which the LLM can't subvert + // through this tool. let http = reqwest::Client::builder() .timeout(SCRAPE_TIMEOUT) - // The ic-observability-stack scrape jobs talk plain HTTP; - // there's no certificate to validate. + .danger_accept_invalid_certs(true) .build() .expect("reqwest client builder is infallible with default features"); Self { @@ -253,14 +328,17 @@ impl IcMetrics { Ok((ip, Some(nid.to_string()))) } - /// Build the scrape URL for a given source + IPv6. + /// Build the scrape URL for a given source + IPv6. The shape + /// (scheme, port, path) varies by source — see the comment block + /// next to the port constants at the top of the file for the full + /// matrix. fn build_url(source: Source, ipv6: Ipv6Addr) -> String { - // Prometheus exposition is on `/metrics` by convention; the - // replica and orchestrator currently expose at `/` (which - // also serves an index page) — we go through `/metrics` for - // all three to be consistent. The IC observability scrape - // jobs use this path. - format!("http://[{}]:{}/metrics", ipv6, source.port()) + format!( + "{scheme}://[{ipv6}]:{port}{path}", + scheme = source.scheme(), + port = source.port(), + path = source.path(), + ) } /// Fetch and parse a scrape from the given URL. @@ -532,21 +610,41 @@ impl Tool for IcMetrics { async fn definition(&self, _prompt: String) -> ToolDefinition { ToolDefinition { name: Self::NAME.to_string(), - description: "Fetch and analyze Prometheus metrics from one of three IC node sources: \ - `replica`, `orchestrator`, or `node_exporter` (guest VM). Operations: \ - `list` to discover metric names, `get` for current values, `summary` \ - for a curated set of operationally important metrics, `rate` for \ - per-second deltas. Use this for performance and resource questions. \ - Targets a specific peer node by `node_id` (resolved via the local \ - registry) or by raw `ipv6`." + description: "Fetch and analyze Prometheus metrics from one of three IC node \ + sources. Pick `source` based on the question:\n\ + \n\ + * `node_exporter` (guest VM) — DEFAULT for general health, performance, \ + and resource questions: CPU load, memory, swap, disk space, disk I/O \ + saturation, network throughput, file descriptors, TCP socket counts, \ + PSI pressure-stall, clock health. The most useful source for \ + troubleshooting; start here.\n\ + * `replica` — IC-specific consensus and state-sync internals: certified \ + height, checkpoints on disk, block production rate, subnet membership, \ + critical-error counter. Use when the question is about IC liveness or \ + state-sync.\n\ + * `orchestrator` — replica supervisor and upgrade manager: CUP \ + deserialization failures, failed upgrade checks, replica restarts, \ + key rotation status, reboot timing. Use for upgrade and orchestration \ + issues.\n\ + \n\ + Operations: `list` discovers metric names (use a `metric` substring \ + to narrow), `get` returns current samples, `summary` returns a curated \ + dashboard for the chosen source, `rate` computes a per-second delta \ + against the previous call. Targets a specific peer node by `node_id` \ + (resolved via the local registry) or by raw `ipv6`." .to_string(), parameters: json!({ "type": "object", "properties": { "source": { "type": "string", - "enum": ["replica", "orchestrator", "node_exporter"], - "description": "Which scrape endpoint to read." + "enum": ["node_exporter", "replica", "orchestrator"], + "description": + "Which scrape endpoint to read. \ + `node_exporter` for host-level CPU/memory/disk/network \ + (preferred default); \ + `replica` for IC consensus/state-sync internals; \ + `orchestrator` for upgrade/CUP/restart issues." }, "op": { "type": "string", From c948e99fffe7736e827ffe14452bb3e33e9db79a Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Tue, 5 May 2026 23:23:23 +0000 Subject: [PATCH 06/11] making ic_logs a todo --- rs/ai_agent/src/config.rs | 26 +- rs/ai_agent/src/main.rs | 4 +- rs/ai_agent/src/providers/mod.rs | 21 +- rs/ai_agent/src/tools/ic_logs.rs | 476 ++---------------------- rs/ai_agent/src/tools/ic_state.rs | 3 +- rs/ai_agent/src/tools/mod.rs | 4 +- rs/ai_agent/src/tools/node_directory.rs | 11 +- rs/ai_agent/src/tools/registry.rs | 7 +- 8 files changed, 73 insertions(+), 479 deletions(-) diff --git a/rs/ai_agent/src/config.rs b/rs/ai_agent/src/config.rs index 22f189c35744..3eb44c204c5f 100644 --- a/rs/ai_agent/src/config.rs +++ b/rs/ai_agent/src/config.rs @@ -16,21 +16,23 @@ pub const DEFAULT_GEMINI_MODEL: &str = "gemini-flash-latest"; /// Default agent system prompt. /// -/// IC observability tools (`ic_state`, `ic_metrics`, `ic_logs`) are described -/// here so the LLM knows to reach for them. Without an explicit mention, the -/// model tends to fall back to "I don't have access to live data" answers -/// instead of using the tools wired into the agent. +/// IC observability tools (`ic_state`, `ic_metrics`) are described here so +/// the LLM knows to reach for them. Without an explicit mention, the model +/// tends to fall back to "I don't have access to live data" answers instead +/// of using the tools wired into the agent. +/// +/// `ic_logs` is intentionally not advertised here — it's a TODO (see +/// `tools/ic_logs.rs`). When it lands, add it back to this prompt with a +/// description of when to prefer it over `ic_metrics`. pub const DEFAULT_PREAMBLE: &str = "You are a concise, helpful assistant. \ When tools are provided, prefer using them for any factual, computational, \ or time-sensitive request. \ \ You can also query Internet Computer node observability: `ic_state` for \ - canister/subnet/node metadata read from the locally synced state, \ - `ic_metrics` for replica/orchestrator/host Prometheus metrics, and \ - `ic_logs` for recent systemd journal output. Prefer `ic_state` for \ - \"what exists\", `ic_metrics` for \"how is it performing\", and \ - `ic_logs` for \"what happened recently\". Always cite the metric name \ - or log timestamp range you used."; + canister/subnet/node metadata read from the locally synced state, and \ + `ic_metrics` for replica/orchestrator/host Prometheus metrics. Prefer \ + `ic_state` for \"what exists\" and `ic_metrics` for \"how is it \ + performing\". Always cite the metric name you used."; /// Default cap on tool-call turns per agent invocation. pub const DEFAULT_MAX_TURNS: usize = 5; @@ -50,8 +52,8 @@ pub struct AppConfig { pub default_preamble: String, pub default_max_turns: usize, /// Path to the replica `ic.json5` config. Used by `ic_state` to discover - /// the on-disk state root, and by `ic_metrics` / `ic_logs` to discover - /// the registry local store (so node ids can be resolved to IPv6 + /// the on-disk state root, and by `ic_metrics` to discover the + /// registry local store (so node ids can be resolved to IPv6 /// addresses of peer nodes in the syncing subnet). pub ic_config_path: PathBuf, } diff --git a/rs/ai_agent/src/main.rs b/rs/ai_agent/src/main.rs index a70f7d901c1a..757f9116e9e7 100644 --- a/rs/ai_agent/src/main.rs +++ b/rs/ai_agent/src/main.rs @@ -21,8 +21,8 @@ struct Cli { addr: SocketAddr, /// Path to the replica `ic.json5` config. Used by `ic_state` to find - /// the on-disk state root, and by `ic_metrics` / `ic_logs` to find - /// the registry local store for resolving peer node IPv6 addresses. + /// the on-disk state root, and by `ic_metrics` to find the registry + /// local store for resolving peer node IPv6 addresses. #[arg(long, env = "IC_AI_AGENT_IC_CONFIG", default_value = DEFAULT_IC_CONFIG_PATH)] ic_config: PathBuf, } diff --git a/rs/ai_agent/src/providers/mod.rs b/rs/ai_agent/src/providers/mod.rs index dd345b3ee00c..348e87d19218 100644 --- a/rs/ai_agent/src/providers/mod.rs +++ b/rs/ai_agent/src/providers/mod.rs @@ -18,7 +18,7 @@ use slog::warn; use crate::{ config::ConfigRequest, state::AppState, - tools::{Calculator, CurrentDateTime, IcLogs, IcMetrics, IcState}, + tools::{Calculator, CurrentDateTime, IcMetrics, IcState}, }; /// Active AI provider client. Currently Gemini-only; new providers slot in @@ -65,12 +65,15 @@ impl AiProvider { /// can reference any of them by name. /// /// Takes `Arc` so the IC observability tools (`ic_state`, - /// `ic_metrics`, `ic_logs`) can pick up the shared replica config - /// path and lazily-built node directory. Tools that fail to - /// construct (typically `ic_state` on a node where `ic.json5` is - /// missing or unreadable) are skipped with a warning rather than - /// failing the whole agent build — the other tools may still be - /// useful and we don't want one bad path to take the agent down. + /// `ic_metrics`) can pick up the shared replica config path and + /// lazily-built node directory. Tools that fail to construct + /// (typically `ic_state` on a node where `ic.json5` is missing or + /// unreadable) are skipped with a warning rather than failing the + /// whole agent build — the other tools may still be useful and we + /// don't want one bad path to take the agent down. + /// + /// `ic_logs` is intentionally not wired up here — it's a TODO, + /// see `tools/ic_logs.rs`. pub async fn build_agent( &self, state: &Arc, @@ -96,9 +99,7 @@ impl AiProvider { } } - builder = builder - .tool(IcMetrics::new(state.clone())) - .tool(IcLogs::new(state.clone())); + builder = builder.tool(IcMetrics::new(state.clone())); Ok(builder.build()) } diff --git a/rs/ai_agent/src/tools/ic_logs.rs b/rs/ai_agent/src/tools/ic_logs.rs index 831d09d5bd8d..b7ae73ebcfc2 100644 --- a/rs/ai_agent/src/tools/ic_logs.rs +++ b/rs/ai_agent/src/tools/ic_logs.rs @@ -1,447 +1,33 @@ -//! Tool: `ic_logs`. +//! Tool: `ic_logs` — **TODO, not implemented**. //! -//! Pulls recent journald entries for an allow-listed systemd unit -//! through `systemd-journal-gatewayd`, which every IC node exposes on -//! port 19531 over HTTP. +//! Intent: pull recent systemd journal entries for an allow-listed +//! systemd unit (e.g. `ic-replica.service`, `ic-orchestrator.service`) +//! from a peer node, with client-side filtering by time window, +//! priority ceiling, and substring grep. The natural transport is +//! `systemd-journal-gatewayd` on port 19531, which every IC node +//! exposes; the previous draft implementation in this file targeted +//! that endpoint with a `Range: entries=...` header and an allow-list +//! of services. //! -//! Gatewayd's journal API is documented at -//! . -//! In short: `GET /entries` with `Accept: application/json` returns -//! one JSON object per entry per line ("application/json-seq" with -//! ASCII-RS framing in newer versions, but most deployments still -//! emit plain newline-delimited JSON; we handle both). Filtering by -//! unit is done with a `_SYSTEMD_UNIT=...` query parameter; line -//! count via `Range: entries=:-N:N`. Time-bound and priority filtering -//! are applied client-side because gatewayd's native filter for those -//! is awkward to compose. - -use std::{net::Ipv6Addr, str::FromStr, sync::Arc, time::Duration}; - -use chrono::{DateTime, TimeZone, Utc}; -use rig::{completion::ToolDefinition, tool::Tool}; -use serde::{Deserialize, Serialize}; -use serde_json::json; - -use crate::state::AppState; - -/// Default port `systemd-journal-gatewayd` listens on. -const GATEWAYD_PORT: u16 = 19531; - -/// HTTP timeout. Generous because gatewayd can be slow on big journals. -const FETCH_TIMEOUT: Duration = Duration::from_secs(20); - -/// Default lookback window when `since` is not supplied. -const DEFAULT_LOOKBACK: Duration = Duration::from_secs(15 * 60); - -/// Default and hard upper bound on lines fetched per call. -const DEFAULT_LINES: u32 = 200; -const MAX_LINES: u32 = 5000; - -/// Default priority ceiling (only entries with `PRIORITY <=` this are -/// returned). 6 = info; matches the spec. -const DEFAULT_PRIORITY: u8 = 6; - -/// Allow-list of systemd units the LLM may query. Keeping this short -/// and explicit is the only thing standing between the LLM and a -/// query like "show me the last 5000 lines of `ssh.service`". Adding -/// a unit here is a deliberate decision. -const ALLOWED_SERVICES: &[&str] = &[ - "ic-replica.service", - "ic-orchestrator.service", - "ic-crypto-csp.service", - "ic-https-outcalls-adapter.service", - "ic-btc-adapter.service", - "node_exporter.service", - "host_node_exporter.service", - "nftables.service", - "chrony.service", -]; - -#[derive(Debug, thiserror::Error)] -pub enum IcLogsError { - #[error("invalid arg: {0}")] - InvalidArg(String), - - #[error("http error: {0}")] - Http(#[from] reqwest::Error), - - #[error("upstream gatewayd returned {status}: {body}")] - Upstream { status: u16, body: String }, - - #[error("invalid timestamp '{value}': {source}")] - Timestamp { - value: String, - #[source] - source: chrono::ParseError, - }, - - #[error("node directory: {0}")] - Directory(#[from] crate::tools::node_directory::NodeDirectoryError), -} - -#[derive(Debug, Deserialize)] -pub struct LogsArgs { - /// systemd unit name. Must be in the built-in allow-list. - pub service: String, - - /// Lines to fetch from gatewayd before client-side filtering. - /// Default 200, capped at 5000. - pub lines: Option, - - /// RFC3339 lower bound. Default: now - 15 minutes. - pub since: Option, - - /// RFC3339 upper bound. Default: now. - pub until: Option, - - /// Min syslog priority (0=emerg .. 7=debug). Default 6 (info). - /// Entries with `PRIORITY > priority` are dropped. - pub priority: Option, - - /// Optional client-side substring filter on the message body - /// (literal `.contains()`, not regex). - pub grep: Option, - - /// Textual `NodeId` to query. Either this or `ipv6` is required. - pub node_id: Option, - - /// Raw IPv6 override (without brackets) for dev/test. - pub ipv6: Option, -} - -#[derive(Debug, Serialize)] -pub struct LogsOutput { - pub service: String, - pub returned: usize, - /// True if gatewayd had more entries within the time window than - /// we asked for. Lets the LLM know to widen `lines` or narrow the - /// time window. - pub truncated: bool, - pub target: TargetInfo, - pub entries: Vec, -} - -#[derive(Debug, Serialize)] -pub struct TargetInfo { - pub node_id: Option, - pub ipv6: String, - pub url: String, -} - -#[derive(Debug, Serialize)] -pub struct LogEntry { - /// RFC3339-formatted timestamp. - pub timestamp: String, - /// Syslog priority (0..=7), or 6 (info) if the entry didn't - /// declare one. - pub priority: u8, - pub unit: String, - pub message: String, -} - -/// Tool struct. -pub struct IcLogs { - state: Arc, - http: reqwest::Client, -} - -impl IcLogs { - pub fn new(state: Arc) -> Self { - let http = reqwest::Client::builder() - .timeout(FETCH_TIMEOUT) - .build() - .expect("reqwest client builder is infallible with default features"); - Self { state, http } - } - - async fn resolve_target( - &self, - node_id: Option<&str>, - ipv6: Option<&str>, - ) -> Result<(Ipv6Addr, Option), IcLogsError> { - if let Some(raw) = ipv6 { - let ip = Ipv6Addr::from_str(raw) - .map_err(|e| IcLogsError::InvalidArg(format!("invalid ipv6 '{raw}': {e}")))?; - return Ok((ip, None)); - } - let nid = node_id.ok_or_else(|| { - IcLogsError::InvalidArg("either node_id or ipv6 must be provided".to_string()) - })?; - let directory = self.state.node_directory().await?; - let ip = directory.resolve_ipv6(nid)?; - Ok((ip, Some(nid.to_string()))) - } -} - -/// Validate that `service` is on the allow-list. -fn check_allowed(service: &str) -> Result<(), IcLogsError> { - if !ALLOWED_SERVICES.contains(&service) { - return Err(IcLogsError::InvalidArg(format!( - "service '{service}' is not on the allow-list ({:?})", - ALLOWED_SERVICES - ))); - } - Ok(()) -} - -fn parse_rfc3339(s: &str) -> Result, IcLogsError> { - DateTime::parse_from_rfc3339(s) - .map(|dt| dt.with_timezone(&Utc)) - .map_err(|e| IcLogsError::Timestamp { - value: s.to_string(), - source: e, - }) -} - -/// Decode one journald-export-as-JSON object emitted by gatewayd into -/// our public `LogEntry`. Best-effort: gatewayd quotes strings except -/// for binary fields which it base64-encodes; our allow-listed -/// services don't emit binary messages so we treat all values as -/// strings. -fn decode_entry(v: &serde_json::Value) -> Option { - let obj = v.as_object()?; - - // Timestamp: gatewayd emits `__REALTIME_TIMESTAMP` as a string of - // microseconds-since-epoch. Newer versions also emit - // `__REALTIME_TIMESTAMP_USEC` and `__REALTIME_TIMESTAMP_NSEC`; we - // only need one. - let ts_us: i64 = obj - .get("__REALTIME_TIMESTAMP") - .and_then(|v| v.as_str()) - .and_then(|s| s.parse::().ok())?; - let secs = ts_us / 1_000_000; - let nsec = ((ts_us % 1_000_000) as u32) * 1_000; - let ts = Utc.timestamp_opt(secs, nsec).single()?; - - let priority: u8 = obj - .get("PRIORITY") - .and_then(|v| v.as_str()) - .and_then(|s| s.parse::().ok()) - .unwrap_or(DEFAULT_PRIORITY); - - let unit = obj - .get("_SYSTEMD_UNIT") - .or_else(|| obj.get("UNIT")) - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let message = obj - .get("MESSAGE") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - Some(LogEntry { - timestamp: ts.to_rfc3339(), - priority, - unit, - message, - }) -} - -impl Tool for IcLogs { - const NAME: &'static str = "ic_logs"; - type Error = IcLogsError; - type Args = LogsArgs; - type Output = LogsOutput; - - async fn definition(&self, _prompt: String) -> ToolDefinition { - // Inline the allow-list into the schema so the LLM sees the - // exact set of accepted services rather than just a free-form - // string field. - let services_json: Vec = ALLOWED_SERVICES - .iter() - .map(|s| serde_json::Value::String((*s).to_string())) - .collect(); - ToolDefinition { - name: Self::NAME.to_string(), - description: "Fetch recent systemd journal logs from one IC node's services. \ - Allowed services include `ic-replica.service`, `ic-orchestrator.service`, \ - and a few system units. Use this for \"what happened\" or \"why did X \ - fail\" questions. Returns structured log entries with timestamp, \ - priority, and message. Targets a specific peer node by `node_id` or \ - raw `ipv6`." - .to_string(), - parameters: json!({ - "type": "object", - "properties": { - "service": { - "type": "string", - "enum": services_json, - "description": "systemd unit to query." - }, - "lines": { - "type": "integer", - "minimum": 1, - "maximum": MAX_LINES, - "description": "Lines to fetch (default 200, max 5000)." - }, - "lines": { - "type": "integer", - "minimum": 1, - "maximum": MAX_LINES, - "description": "Lines to fetch (default 200, max 5000)." - }, - "since": { - "type": "string", - "description": "RFC3339 lower bound. Default: now - 15 minutes." - }, - "until": { - "type": "string", - "description": "RFC3339 upper bound. Default: now." - }, - "priority": { - "type": "integer", - "minimum": 0, - "maximum": 7, - "description": - "Min syslog priority (0=emerg .. 7=debug). Entries with \ - higher numeric priority are dropped. Default 6 (info)." - }, - "grep": { - "type": "string", - "description": - "Optional substring filter (literal, not regex)." - }, - "node_id": { - "type": "string", - "description": - "Textual NodeId of the peer to query. Resolved via the \ - local registry. Either node_id or ipv6 is required." - }, - "ipv6": { - "type": "string", - "description": - "Raw IPv6 of the peer (without brackets). Override for \ - dev/test." - } - }, - "required": ["service"] - }), - } - } - - async fn call(&self, args: Self::Args) -> Result { - check_allowed(&args.service)?; - - let lines = args.lines.unwrap_or(DEFAULT_LINES).clamp(1, MAX_LINES); - let priority = args.priority.unwrap_or(DEFAULT_PRIORITY); - if priority > 7 { - return Err(IcLogsError::InvalidArg(format!( - "priority must be 0..=7, got {priority}" - ))); - } - - let now = Utc::now(); - let since = match args.since.as_deref() { - Some(s) => parse_rfc3339(s)?, - None => now - chrono::Duration::from_std(DEFAULT_LOOKBACK).unwrap(), - }; - let until = match args.until.as_deref() { - Some(s) => parse_rfc3339(s)?, - None => now, - }; - if since > until { - return Err(IcLogsError::InvalidArg(format!( - "since {since} is after until {until}" - ))); - } - - let (ipv6, resolved_node_id) = self - .resolve_target(args.node_id.as_deref(), args.ipv6.as_deref()) - .await?; - - // gatewayd field-equality filter is encoded into the URL as - // `?_SYSTEMD_UNIT=foo.service`; the `Range: entries=:-N:N` - // header asks for the last N entries. - let url = format!( - "http://[{ipv6}]:{port}/entries?_SYSTEMD_UNIT={service}", - ipv6 = ipv6, - port = GATEWAYD_PORT, - service = args.service, - ); - let range = format!("entries=:-{lines}:{lines}"); - - let resp = self - .http - .get(&url) - .header(reqwest::header::ACCEPT, "application/json") - .header(reqwest::header::RANGE, range) - .send() - .await?; - let status = resp.status(); - if !status.is_success() { - let body = resp.text().await.unwrap_or_default(); - return Err(IcLogsError::Upstream { - status: status.as_u16(), - body: truncate(&body, 512), - }); - } - let body = resp.text().await?; - - // Most gatewayd builds emit one JSON object per line. Some - // newer builds use `application/json-seq` (RS-prefixed). We - // accept both by stripping leading `0x1e` (record separator) - // before parsing each line. - let mut entries: Vec = Vec::new(); - let mut total_seen: usize = 0; - for line in body.lines() { - let line = line.trim_start_matches('\u{1e}').trim(); - if line.is_empty() { - continue; - } - total_seen += 1; - let v: serde_json::Value = match serde_json::from_str(line) { - Ok(v) => v, - Err(_) => continue, - }; - let Some(entry) = decode_entry(&v) else { - continue; - }; - // Filter: time window. - let ts = match DateTime::parse_from_rfc3339(&entry.timestamp) { - Ok(t) => t.with_timezone(&Utc), - Err(_) => continue, - }; - if ts < since || ts > until { - continue; - } - // Filter: priority ceiling. - if entry.priority > priority { - continue; - } - // Filter: grep (literal substring, not regex). - if let Some(needle) = args.grep.as_deref() - && !needle.is_empty() - && !entry.message.contains(needle) - { - continue; - } - entries.push(entry); - } - - let truncated = total_seen as u32 >= lines && (entries.len() as u32) >= lines; - let returned = entries.len(); - - Ok(LogsOutput { - service: args.service, - returned, - truncated, - target: TargetInfo { - node_id: resolved_node_id, - ipv6: ipv6.to_string(), - url, - }, - entries, - }) - } -} - -fn truncate(s: &str, n: usize) -> String { - if s.len() <= n { - s.to_string() - } else { - format!("{}…", &s[..n]) - } -} +//! Why it's stubbed for now: the `gatewayd` socket isn't reachable +//! over the IPv6 we resolve from the registry on AI nodes today, so +//! shipping the tool would mean shipping something that always +//! returns transport errors. We'd rather expose the gap explicitly +//! than have the LLM keep retrying a broken endpoint. +//! +//! When picking this back up: +//! * Confirm the journal-gatewayd port and TLS posture across guestos +//! variants (it may need the same `danger_accept_invalid_certs(true)` +//! treatment as `node_exporter`). +//! * Bring back the allow-list of systemd units (replica, orchestrator, +//! crypto-csp, btc/https-outcalls adapters, node_exporter, nftables, +//! chrony) — this is the only thing keeping the LLM from being able +//! to ask for arbitrary host logs. +//! * Re-add the registration in `tools/mod.rs`, `tools/registry.rs`, +//! `providers/mod.rs::build_agent`, and the preamble in `config.rs`. +//! +//! The shared `NodeDirectory` (in `tools/node_directory.rs`) is +//! already wired up for this — `ic_logs` will resolve `node_id -> +//! ipv6` the same way `ic_metrics` does, so reviving the tool is +//! purely a matter of restoring the gatewayd HTTP client + the +//! filtering logic. diff --git a/rs/ai_agent/src/tools/ic_state.rs b/rs/ai_agent/src/tools/ic_state.rs index cf48d93c25c4..e137cb909ff2 100644 --- a/rs/ai_agent/src/tools/ic_state.rs +++ b/rs/ai_agent/src/tools/ic_state.rs @@ -462,8 +462,7 @@ impl Tool for IcState { description: "Query the Internet Computer state for canister, subnet, or node \ information. Use this when the operator asks about what's deployed, \ who controls a canister, cycles balance, module hash, or node \ - membership. Does not return metrics or logs — use `ic_metrics` or \ - `ic_logs` for those." + membership. Does not return metrics — use `ic_metrics` for that." .to_string(), parameters: json!({ "type": "object", diff --git a/rs/ai_agent/src/tools/mod.rs b/rs/ai_agent/src/tools/mod.rs index 5503fa81a768..8250bf40f3c6 100644 --- a/rs/ai_agent/src/tools/mod.rs +++ b/rs/ai_agent/src/tools/mod.rs @@ -8,6 +8,9 @@ pub mod calculator; pub mod current_datetime; +// `ic_logs` is currently a TODO stub — see the module docs in +// `ic_logs.rs`. Kept in the tree (not deleted) so the placeholder +// stays visible to anyone browsing the tools directory. pub mod ic_logs; pub mod ic_metrics; pub mod ic_state; @@ -16,7 +19,6 @@ pub mod registry; pub use calculator::Calculator; pub use current_datetime::CurrentDateTime; -pub use ic_logs::IcLogs; pub use ic_metrics::IcMetrics; pub use ic_state::IcState; pub use registry::{registered_tool_names, validate_tool_names}; diff --git a/rs/ai_agent/src/tools/node_directory.rs b/rs/ai_agent/src/tools/node_directory.rs index dd9d5c9c2cf5..3a810487385a 100644 --- a/rs/ai_agent/src/tools/node_directory.rs +++ b/rs/ai_agent/src/tools/node_directory.rs @@ -1,11 +1,12 @@ //! Shared helper: resolve `NodeId -> Ipv6Addr` from the local registry //! store. //! -//! Both `ic_metrics` and `ic_logs` need to talk to a peer node in the -//! subnet that this AiNode is shadowing. The LLM passes a textual node -//! id (which it discovered via `ic_state`); we look it up in the -//! registry the orchestrator-managed state-sync replica keeps on disk -//! and return the IPv6 published in the node's `NodeRecord.http`. +//! `ic_metrics` (and, when implemented, `ic_logs`) needs to talk to a +//! peer node in the subnet that this AiNode is shadowing. The LLM +//! passes a textual node id (which it discovered via `ic_state`); we +//! look it up in the registry the orchestrator-managed state-sync +//! replica keeps on disk and return the IPv6 published in the node's +//! `NodeRecord.http`. //! //! Single instance per process. Holds a `RegistryClientImpl` backed by //! a `LocalStoreImpl`. We never spawn the background polling thread — diff --git a/rs/ai_agent/src/tools/registry.rs b/rs/ai_agent/src/tools/registry.rs index b098ff481504..d4e07760bb37 100644 --- a/rs/ai_agent/src/tools/registry.rs +++ b/rs/ai_agent/src/tools/registry.rs @@ -5,16 +5,19 @@ use rig::tool::Tool; -use super::{Calculator, CurrentDateTime, IcLogs, IcMetrics, IcState}; +use super::{Calculator, CurrentDateTime, IcMetrics, IcState}; /// Returns the names of all built-in tools. +/// +/// `ic_logs` is intentionally absent: the module is currently a TODO +/// stub (see `ic_logs.rs`). It will be added back here once the +/// implementation lands. pub fn registered_tool_names() -> &'static [&'static str] { &[ Calculator::NAME, CurrentDateTime::NAME, IcState::NAME, IcMetrics::NAME, - IcLogs::NAME, ] } From 4ae0acd7420367cda76f80b587076c6cca8e027d Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Tue, 5 May 2026 23:44:22 +0000 Subject: [PATCH 07/11] fixing chatting --- Cargo.lock | 1 + rs/ai_agent/BUILD.bazel | 1 + rs/ai_agent/Cargo.toml | 1 + rs/ai_agent/src/config.rs | 12 +- rs/ai_agent/src/handlers/chat.rs | 137 +++++++++++++------- rs/ai_agent/src/handlers/config.rs | 9 +- rs/ai_agent/src/handlers/mod.rs | 1 + rs/ai_agent/src/handlers/sessions.rs | 61 +++++++++ rs/ai_agent/src/lib.rs | 1 + rs/ai_agent/src/main.rs | 15 ++- rs/ai_agent/src/models/mod.rs | 4 +- rs/ai_agent/src/models/request.rs | 30 ++--- rs/ai_agent/src/models/response.rs | 33 ++--- rs/ai_agent/src/router.rs | 14 +- rs/ai_agent/src/sessions.rs | 185 +++++++++++++++++++++++++++ rs/ai_agent/src/state.rs | 6 + 16 files changed, 424 insertions(+), 87 deletions(-) create mode 100644 rs/ai_agent/src/handlers/sessions.rs create mode 100644 rs/ai_agent/src/sessions.rs diff --git a/Cargo.lock b/Cargo.lock index e03cc5b803c2..3b6dcbdcf532 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6565,6 +6565,7 @@ dependencies = [ "tokio", "tower 0.5.3", "tower-http", + "uuid", ] [[package]] diff --git a/rs/ai_agent/BUILD.bazel b/rs/ai_agent/BUILD.bazel index 29c0d5ba289a..64a8707c31b5 100644 --- a/rs/ai_agent/BUILD.bazel +++ b/rs/ai_agent/BUILD.bazel @@ -32,6 +32,7 @@ DEPENDENCIES = [ "@crate_index//:tokio", "@crate_index//:tower", "@crate_index//:tower-http", + "@crate_index//:uuid", ] rust_library( diff --git a/rs/ai_agent/Cargo.toml b/rs/ai_agent/Cargo.toml index 9e07855c0d0f..6e59d2f1e820 100644 --- a/rs/ai_agent/Cargo.toml +++ b/rs/ai_agent/Cargo.toml @@ -41,3 +41,4 @@ thiserror = { workspace = true } tokio = { workspace = true } tower = { workspace = true } tower-http = { workspace = true } +uuid = { workspace = true } diff --git a/rs/ai_agent/src/config.rs b/rs/ai_agent/src/config.rs index 3eb44c204c5f..b8b5b6d89a70 100644 --- a/rs/ai_agent/src/config.rs +++ b/rs/ai_agent/src/config.rs @@ -5,7 +5,9 @@ //! the spec). Defaults below cover the rest. use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::{path::PathBuf, time::Duration}; + +use crate::sessions::{DEFAULT_IDLE_TTL, DEFAULT_MAX_SESSIONS}; /// Default Gemini model used if `/v1/config` doesn't override it. /// @@ -56,6 +58,12 @@ pub struct AppConfig { /// registry local store (so node ids can be resolved to IPv6 /// addresses of peer nodes in the syncing subnet). pub ic_config_path: PathBuf, + /// Maximum number of concurrently-cached chat sessions. Tuned for + /// AI-node operator workloads, not for serving end users at scale. + pub max_sessions: usize, + /// Per-session idle TTL. A session that hasn't received a turn in + /// this long is dropped on next access. + pub session_idle_ttl: Duration, } impl Default for AppConfig { @@ -65,6 +73,8 @@ impl Default for AppConfig { default_preamble: DEFAULT_PREAMBLE.to_string(), default_max_turns: DEFAULT_MAX_TURNS, ic_config_path: PathBuf::from(DEFAULT_IC_CONFIG_PATH), + max_sessions: DEFAULT_MAX_SESSIONS, + session_idle_ttl: DEFAULT_IDLE_TTL, } } } diff --git a/rs/ai_agent/src/handlers/chat.rs b/rs/ai_agent/src/handlers/chat.rs index d5daedcb2acf..6459224f4367 100644 --- a/rs/ai_agent/src/handlers/chat.rs +++ b/rs/ai_agent/src/handlers/chat.rs @@ -1,22 +1,32 @@ use std::sync::Arc; use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; -use rig::completion::{Prompt, message::Message}; -use slog::warn; +use rig::completion::Prompt; +use slog::{info, warn}; use crate::{ - models::{ - ChatMessage, ChatRequest, ChatResponse, ErrorBody, request::ChatRole, - response::SerializableChatMessage, - }, - providers::provider_not_configured, + models::{ChatRequest, ChatResponse, ErrorBody}, + providers::{AiProvider, provider_not_configured}, state::AppState, tools::validate_tool_names, }; -/// `POST /v1/agent/chat` — multi-turn agent invocation, with caller-managed -/// history. The server appends the new exchange to `history` and returns the -/// updated transcript. +/// `POST /v1/agent/chat` — multi-turn agent invocation with +/// server-managed transcript. +/// +/// Wire shape: +/// * Without `session_id`: the server creates a fresh session with +/// an empty transcript, runs the prompt, and returns the freshly- +/// minted session id alongside the response. +/// * With `session_id`: the server replays the cached transcript +/// into the agent, runs the new prompt, and appends the resulting +/// turn (user message + any tool-call interleavings + assistant +/// reply) back into the cached transcript. +/// +/// The agent itself is rebuilt per request — that's cheap (struct +/// construction + tool wiring), and it picks up `POST /v1/config` +/// changes naturally. Per-request `preamble`, `tools`, and +/// `max_turns` always apply. pub async fn chat( State(state): State>, Json(req): Json, @@ -36,18 +46,27 @@ pub async fn chat( .into_response(); } - let provider_guard = state.provider.read().await; - let provider = match provider_guard.as_ref() { - Some(p) => p.clone(), - None => { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ErrorBody::new(provider_not_configured().to_string())), - ) - .into_response(); - } + // Resolve the provider once for this request. + let provider = match read_provider(&state).await { + Ok(p) => p, + Err(resp) => return resp, + }; + + // Pull the cached transcript (if any). The snapshot is owned — + // we never hold the store mutex across the LLM `.await`. + let (mut history, session_id, is_new) = match req.session_id.as_deref() { + Some(id) => match state.sessions.get(id) { + Some(snap) => (snap.history, id.to_string(), false), + None => { + // Caller passed an id we don't know (or that has + // expired). Honour the id by keying the new session + // under it, rather than minting a different one and + // leaving the caller's id orphaned. + (Vec::new(), id.to_string(), true) + } + }, + None => (Vec::new(), crate::sessions::SessionStore::fresh_id(), true), }; - drop(provider_guard); let preamble = req .preamble @@ -66,42 +85,63 @@ pub async fn chat( } }; - let history_msgs: Vec = req.history.iter().map(to_rig_message).collect(); - + // We use `prompt(...).with_history(...).extended_details()` rather + // than `Chat::chat(...)` because the former returns a + // `PromptResponse` whose `messages` field carries the new turns + // (user prompt + tool calls/results + final assistant) in the + // right order. That's exactly what we need to append to the + // cached transcript so the next turn sees a faithful history, + // including any tool-call interleavings. let result = agent .prompt(req.prompt.as_str()) - .with_history(history_msgs) + .with_history(history.clone()) .max_turns(max_turns) .extended_details() .await; match result { Ok(resp) => { - let turns_used = resp.messages.as_ref().map(Vec::len).unwrap_or(1); - let mut full_history: Vec = req - .history - .iter() - .map(SerializableChatMessage::from) - .collect(); - full_history.push(SerializableChatMessage::from(&ChatMessage { - role: ChatRole::User, - content: req.prompt.clone(), - })); - full_history.push(SerializableChatMessage::from(&ChatMessage { - role: ChatRole::Assistant, - content: resp.output.clone(), - })); + // Extend the snapshot with the new turns produced by this + // request. `messages` is `Option>`; rig only + // sets `None` when no new turn happened (defensive — in + // practice it's always populated for prompt requests). + if let Some(new_turns) = resp.messages.as_ref() { + history.extend(new_turns.iter().cloned()); + } + + // Persist the updated transcript. For new sessions this + // is the first `put`; for existing ones it's an + // `update_history` (which silently no-ops if the session + // was evicted while we were `await`ing — see the comment + // on `SessionStore::update_history`). + if is_new { + state.sessions.put( + Some(session_id.clone()), + history, + provider.name(), + provider.model().to_string(), + ); + info!( + state.log, + "created chat session"; + "session_id" => &session_id, + "provider" => provider.name(), + "model" => provider.model() + ); + } else { + state.sessions.update_history(&session_id, history); + } + let body = ChatResponse { response: resp.output, - history: full_history, - turns_used, + session_id, provider: provider.name().to_string(), model: provider.model().to_string(), }; (StatusCode::OK, Json(body)).into_response() } Err(e) => { - warn!(state.log, "agent chat failed"; "error" => %e); + warn!(state.log, "agent chat failed"; "error" => %e, "session_id" => &session_id); ( StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorBody::new(format!("Agent failed: {e}"))), @@ -111,9 +151,16 @@ pub async fn chat( } } -fn to_rig_message(m: &ChatMessage) -> Message { - match m.role { - ChatRole::User => Message::user(m.content.clone()), - ChatRole::Assistant => Message::assistant(m.content.clone()), +/// Read the active provider, returning a 503 response if `/v1/config` +/// hasn't been called yet. +async fn read_provider(state: &Arc) -> Result { + let guard = state.provider.read().await; + match guard.as_ref() { + Some(p) => Ok(p.clone()), + None => Err(( + StatusCode::SERVICE_UNAVAILABLE, + Json(ErrorBody::new(provider_not_configured().to_string())), + ) + .into_response()), } } diff --git a/rs/ai_agent/src/handlers/config.rs b/rs/ai_agent/src/handlers/config.rs index 03f7c1668c4c..c793dee30123 100644 --- a/rs/ai_agent/src/handlers/config.rs +++ b/rs/ai_agent/src/handlers/config.rs @@ -26,11 +26,18 @@ pub async fn configure( model: provider.model().to_string(), }; *state.provider.write().await = Some(provider); + // Drop all cached chat sessions: their Agents are bound + // to whichever GeminiClient (= API key) was active when + // they were built. After a re-config they would silently + // keep using the previous credentials, which is exactly + // the wrong behaviour after a key rotation. + let cleared = state.sessions.clear(); info!( state.log, "provider configured"; "provider" => &resp.provider, - "model" => &resp.model + "model" => &resp.model, + "sessions_cleared" => cleared ); (StatusCode::OK, Json(resp)).into_response() } diff --git a/rs/ai_agent/src/handlers/mod.rs b/rs/ai_agent/src/handlers/mod.rs index ed7870407bc0..8096e55d19e3 100644 --- a/rs/ai_agent/src/handlers/mod.rs +++ b/rs/ai_agent/src/handlers/mod.rs @@ -2,3 +2,4 @@ pub mod chat; pub mod config; pub mod health; pub mod run; +pub mod sessions; diff --git a/rs/ai_agent/src/handlers/sessions.rs b/rs/ai_agent/src/handlers/sessions.rs new file mode 100644 index 000000000000..81394a8db2c7 --- /dev/null +++ b/rs/ai_agent/src/handlers/sessions.rs @@ -0,0 +1,61 @@ +//! `DELETE /v1/agent/sessions[/:id]` — drop cached chat sessions. +//! +//! Two routes: +//! * `DELETE /v1/agent/sessions/:id` — drop one session. +//! * `DELETE /v1/agent/sessions` — drop all of them. +//! +//! Both return `200` with `{ "status": "ok", "cleared": }`. The +//! single-id path returns `404` when the id is unknown. + +use std::sync::Arc; + +use axum::{ + Json, + extract::{Path, State}, + http::StatusCode, + response::IntoResponse, +}; +use slog::info; + +use crate::{ + models::{ClearResponse, ErrorBody}, + state::AppState, +}; + +/// Drop one session by id. +pub async fn delete_one( + State(state): State>, + Path(id): Path, +) -> impl IntoResponse { + if state.sessions.remove(&id) { + info!(state.log, "session deleted"; "session_id" => &id); + ( + StatusCode::OK, + Json(ClearResponse { + status: "ok", + cleared: 1, + }), + ) + .into_response() + } else { + ( + StatusCode::NOT_FOUND, + Json(ErrorBody::new(format!("unknown session_id: {id}"))), + ) + .into_response() + } +} + +/// Drop every cached session. +pub async fn delete_all(State(state): State>) -> impl IntoResponse { + let cleared = state.sessions.clear(); + info!(state.log, "all sessions cleared"; "count" => cleared); + ( + StatusCode::OK, + Json(ClearResponse { + status: "ok", + cleared, + }), + ) + .into_response() +} diff --git a/rs/ai_agent/src/lib.rs b/rs/ai_agent/src/lib.rs index ef98e6c4e33f..0f65b8c740dc 100644 --- a/rs/ai_agent/src/lib.rs +++ b/rs/ai_agent/src/lib.rs @@ -11,5 +11,6 @@ pub mod handlers; pub mod models; pub mod providers; pub mod router; +pub mod sessions; pub mod state; pub mod tools; diff --git a/rs/ai_agent/src/main.rs b/rs/ai_agent/src/main.rs index 757f9116e9e7..7409837d0124 100644 --- a/rs/ai_agent/src/main.rs +++ b/rs/ai_agent/src/main.rs @@ -8,10 +8,11 @@ use clap::Parser; use ic_ai_agent::{ config::{AppConfig, DEFAULT_IC_CONFIG_PATH}, router::build_router, + sessions::{DEFAULT_IDLE_TTL, DEFAULT_MAX_SESSIONS}, state::AppState, }; use slog::{Drain, Logger, info, o}; -use std::{net::SocketAddr, path::PathBuf, sync::Arc}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; #[derive(Debug, Parser)] #[command(name = "ic-ai-agent", about = "IC AI agent orchestration HTTP API")] @@ -25,6 +26,16 @@ struct Cli { /// local store for resolving peer node IPv6 addresses. #[arg(long, env = "IC_AI_AGENT_IC_CONFIG", default_value = DEFAULT_IC_CONFIG_PATH)] ic_config: PathBuf, + + /// Maximum number of concurrently-cached chat sessions before the + /// LRU starts evicting. + #[arg(long, env = "IC_AI_AGENT_MAX_SESSIONS", default_value_t = DEFAULT_MAX_SESSIONS)] + max_sessions: usize, + + /// Per-session idle TTL in seconds. Sessions untouched for longer + /// than this are evicted on next access. + #[arg(long, env = "IC_AI_AGENT_SESSION_IDLE_TTL_SECS", default_value_t = DEFAULT_IDLE_TTL.as_secs())] + session_idle_ttl_secs: u64, } fn make_logger() -> Logger { @@ -55,6 +66,8 @@ async fn main() -> anyhow::Result<()> { let config = AppConfig { ic_config_path: cli.ic_config.clone(), + max_sessions: cli.max_sessions, + session_idle_ttl: Duration::from_secs(cli.session_idle_ttl_secs), ..AppConfig::default() }; let state = Arc::new(AppState::new(config, log.clone())); diff --git a/rs/ai_agent/src/models/mod.rs b/rs/ai_agent/src/models/mod.rs index f6630fd290be..23e8bf76624e 100644 --- a/rs/ai_agent/src/models/mod.rs +++ b/rs/ai_agent/src/models/mod.rs @@ -3,5 +3,5 @@ pub mod request; pub mod response; -pub use request::{ChatMessage, ChatRequest, RunRequest}; -pub use response::{ChatResponse, ErrorBody, HealthResponse, RunResponse}; +pub use request::{ChatRequest, RunRequest}; +pub use response::{ChatResponse, ClearResponse, ErrorBody, HealthResponse, RunResponse}; diff --git a/rs/ai_agent/src/models/request.rs b/rs/ai_agent/src/models/request.rs index 5dabebd788f6..03d82a2e87f2 100644 --- a/rs/ai_agent/src/models/request.rs +++ b/rs/ai_agent/src/models/request.rs @@ -12,26 +12,24 @@ pub struct RunRequest { pub max_turns: Option, } -/// Single message in a chat history. -#[derive(Debug, Clone, Deserialize)] -pub struct ChatMessage { - pub role: ChatRole, - pub content: String, -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "lowercase")] -pub enum ChatRole { - User, - Assistant, -} - /// Body of `POST /v1/agent/chat`. +/// +/// The session_id is what makes this multi-turn: omit it on the first +/// call to start a new session (the server returns the freshly-minted +/// id), then echo the same id on every subsequent turn. The server +/// caches one configured Agent per session id and only the new user +/// prompt needs to be sent each time. +/// +/// `preamble`, `tools`, and `max_turns` are honoured only at session- +/// creation time. They are ignored on subsequent turns of an existing +/// session — the cached Agent's configuration wins. To change them, +/// start a new session. #[derive(Debug, Deserialize)] pub struct ChatRequest { pub prompt: String, - #[serde(default)] - pub history: Vec, + /// Existing session id. If omitted, a new session is created and + /// the id is returned in the response. + pub session_id: Option, pub preamble: Option, #[serde(default)] pub tools: Vec, diff --git a/rs/ai_agent/src/models/response.rs b/rs/ai_agent/src/models/response.rs index c5831e5ef885..e1705dacc85a 100644 --- a/rs/ai_agent/src/models/response.rs +++ b/rs/ai_agent/src/models/response.rs @@ -1,7 +1,5 @@ use serde::Serialize; -use super::request::ChatMessage; - #[derive(Debug, Serialize)] pub struct HealthResponse { pub status: &'static str, @@ -17,31 +15,28 @@ pub struct RunResponse { pub model: String, } +/// Body of a successful `POST /v1/agent/chat` response. +/// +/// `session_id` is always present — either echoed back or freshly +/// minted on first turn. Clients should persist it and pass it on +/// every subsequent turn. #[derive(Debug, Serialize)] pub struct ChatResponse { pub response: String, - pub history: Vec, - pub turns_used: usize, + pub session_id: String, pub provider: String, pub model: String, } +/// Body of `DELETE /v1/agent/sessions` and +/// `DELETE /v1/agent/sessions/:id`. #[derive(Debug, Serialize)] -pub struct SerializableChatMessage { - pub role: String, - pub content: String, -} - -impl From<&ChatMessage> for SerializableChatMessage { - fn from(m: &ChatMessage) -> Self { - Self { - role: match m.role { - super::request::ChatRole::User => "user".to_string(), - super::request::ChatRole::Assistant => "assistant".to_string(), - }, - content: m.content.clone(), - } - } +pub struct ClearResponse { + /// "ok" on success. + pub status: &'static str, + /// How many sessions were dropped (0 or 1 for single-session + /// delete; 0..=N for the bulk delete). + pub cleared: usize, } #[derive(Debug, Serialize)] diff --git a/rs/ai_agent/src/router.rs b/rs/ai_agent/src/router.rs index e6e8dd787dcf..d91b474245dc 100644 --- a/rs/ai_agent/src/router.rs +++ b/rs/ai_agent/src/router.rs @@ -4,12 +4,18 @@ use std::sync::Arc; use axum::{ Router, - routing::{get, post}, + routing::{delete, get, post}, }; use tower_http::trace::TraceLayer; use crate::{ - handlers::{chat::chat, config::configure, health::health, run::run}, + handlers::{ + chat::chat, + config::configure, + health::health, + run::run, + sessions::{delete_all as delete_all_sessions, delete_one as delete_one_session}, + }, state::AppState, }; @@ -19,6 +25,10 @@ pub fn build_router(state: Arc) -> Router { .route("/v1/config", post(configure)) .route("/v1/agent/run", post(run)) .route("/v1/agent/chat", post(chat)) + // Drop a single chat session (404 if unknown). + .route("/v1/agent/sessions/{id}", delete(delete_one_session)) + // Wipe every cached session at once. + .route("/v1/agent/sessions", delete(delete_all_sessions)) .layer(TraceLayer::new_for_http()) .with_state(state) } diff --git a/rs/ai_agent/src/sessions.rs b/rs/ai_agent/src/sessions.rs new file mode 100644 index 000000000000..8bb4f5f2af30 --- /dev/null +++ b/rs/ai_agent/src/sessions.rs @@ -0,0 +1,185 @@ +//! In-memory chat session store. +//! +//! `/v1/agent/chat` is multi-turn: callers send only the new user +//! prompt plus a `session_id`, and the server is responsible for +//! correlating turns. We cache the conversation transcript +//! (`Vec`) per session id; the rig `Agent` itself is rebuilt +//! per turn (it's cheap — just struct construction and tool wiring) +//! so that re-keying via `POST /v1/config` and per-request preamble +//! changes naturally take effect. +//! +//! Bounding rules (so a misbehaving client can't OOM the process): +//! +//! * `LruCache` capped at a configurable count +//! (default [`DEFAULT_MAX_SESSIONS`]). +//! * Per-session idle TTL ([`DEFAULT_IDLE_TTL`] default). Idle entries +//! are swept lazily on every access — no background task required. +//! +//! Reset semantics: +//! +//! * `DELETE /v1/agent/sessions/:id` and `DELETE /v1/agent/sessions` +//! call into [`SessionStore::remove`] / [`SessionStore::clear`]. +//! * `POST /v1/config` clears all sessions, because per-session +//! transcripts under a different model/key tend to confuse the +//! model on continuation. + +use std::{ + sync::Mutex, + time::{Duration, Instant}, +}; + +use lru::LruCache; +use rig::completion::message::Message; +use uuid::Uuid; + +/// Default cap on concurrently-cached sessions. ~256 chat threads +/// covers any realistic AI-node operator workload; raise via +/// `--max-sessions` if you actually need more. +pub const DEFAULT_MAX_SESSIONS: usize = 256; + +/// Default idle TTL. A session that hasn't received a turn in this +/// long is dropped on the next access. +pub const DEFAULT_IDLE_TTL: Duration = Duration::from_secs(60 * 60); + +/// One cached chat thread. +pub struct Session { + /// Conversation transcript. Replayed verbatim into the next + /// `agent.prompt(...).with_history(history)` call. After each + /// turn we extend this with the `messages` list that rig returns + /// in its `PromptResponse` — that list contains the new user + /// prompt, any tool-call/tool-result interleavings, and the final + /// assistant reply, in the right order for the next turn. + pub history: Vec, + + /// Provider name + model snapshot, copied for the response so + /// callers can verify which model handled their turn. + pub provider_name: &'static str, + pub model: String, + + /// Last-touched timestamp for idle-eviction. + pub last_activity: Instant, +} + +/// Thread-safe LRU + TTL session store. Wraps a `Mutex` — +/// the critical section is a hashmap lookup + clone of the history +/// `Vec` and is never held across an `.await`. +pub struct SessionStore { + inner: Mutex>, + idle_ttl: Duration, +} + +impl SessionStore { + pub fn new(max_sessions: usize, idle_ttl: Duration) -> Self { + let cap = max_sessions.max(1); + Self { + inner: Mutex::new(LruCache::new(cap)), + idle_ttl, + } + } + + /// Generate a fresh, server-side session id. UUIDv4 keeps + /// collisions a non-issue and matches what most chat APIs use. + pub fn fresh_id() -> String { + Uuid::new_v4().to_string() + } + + /// Returns a snapshot of the cached session for `id`, refreshing + /// its `last_activity`. Returns `None` if the session does not + /// exist or has expired (in which case the entry is also evicted). + /// + /// The returned [`SessionSnapshot`] holds an owned clone of the + /// transcript; the caller can `await` on the LLM without keeping + /// the store mutex. + pub fn get(&self, id: &str) -> Option { + let mut cache = self.inner.lock().unwrap(); + + // Lazy idle-TTL sweep on the requested entry. We don't sweep + // the whole cache — capacity is bounded so stragglers will be + // pushed out by the LRU on their own. + if let Some(s) = cache.peek(id) + && s.last_activity.elapsed() > self.idle_ttl + { + cache.pop(id); + return None; + } + + let session = cache.get_mut(id)?; + session.last_activity = Instant::now(); + Some(SessionSnapshot { + history: session.history.clone(), + provider_name: session.provider_name, + model: session.model.clone(), + }) + } + + /// Insert (or replace) a session. Returns the id we keyed it + /// under, which may be a freshly-minted UUID if the caller + /// passed `None`. + pub fn put( + &self, + id: Option, + history: Vec, + provider_name: &'static str, + model: String, + ) -> String { + let id = id.unwrap_or_else(Self::fresh_id); + let mut cache = self.inner.lock().unwrap(); + cache.put( + id.clone(), + Session { + history, + provider_name, + model, + last_activity: Instant::now(), + }, + ); + id + } + + /// Replace the cached transcript for `id` after a turn completes. + /// + /// If the session was evicted between the snapshot read and this + /// write (eviction races: another concurrent request, or LRU + /// pressure from a different session id) we silently no-op rather + /// than re-creating the entry — the user's intent was to update + /// an existing session, not to resurrect one that was just told + /// to go away. The next turn will then start fresh under the + /// same id (or a new one) without surprising history. + pub fn update_history(&self, id: &str, history: Vec) { + let mut cache = self.inner.lock().unwrap(); + if let Some(session) = cache.get_mut(id) { + session.history = history; + session.last_activity = Instant::now(); + } + } + + /// Remove one session by id. Returns `true` if it was present. + pub fn remove(&self, id: &str) -> bool { + self.inner.lock().unwrap().pop(id).is_some() + } + + /// Drop every session. Used by `DELETE /v1/agent/sessions` and on + /// every `POST /v1/config` (because mixing transcripts across + /// model/credential changes is more confusing than helpful). + pub fn clear(&self) -> usize { + let mut cache = self.inner.lock().unwrap(); + let n = cache.len(); + cache.clear(); + n + } + + /// Number of cached sessions. Intended for diagnostics / tests. + #[allow(dead_code)] + pub fn len(&self) -> usize { + self.inner.lock().unwrap().len() + } +} + +/// Read snapshot of a session. The transcript is owned (cloned out +/// of the LRU) so handlers can drop the store mutex before issuing +/// the LLM call. +pub struct SessionSnapshot { + pub history: Vec, + pub provider_name: &'static str, + pub model: String, +} diff --git a/rs/ai_agent/src/state.rs b/rs/ai_agent/src/state.rs index 248408570935..eddcb516a180 100644 --- a/rs/ai_agent/src/state.rs +++ b/rs/ai_agent/src/state.rs @@ -7,6 +7,7 @@ use crate::{ config::AppConfig, providers::AiProvider, + sessions::SessionStore, tools::node_directory::{NodeDirectory, NodeDirectoryError}, }; use slog::Logger; @@ -24,15 +25,20 @@ pub struct AppState { /// `AppState::new` to stay infallible, and (b) on a fresh AiNode /// the registry local store may not exist yet on startup. node_directory: OnceCell, NodeDirectoryError>>, + /// In-memory chat-session cache for `/v1/agent/chat`. See + /// [`crate::sessions`] for bounding rules. + pub sessions: SessionStore, } impl AppState { pub fn new(config: AppConfig, log: Logger) -> Self { + let sessions = SessionStore::new(config.max_sessions, config.session_idle_ttl); Self { config, log, provider: RwLock::new(None), node_directory: OnceCell::new(), + sessions, } } From 280d0bae659bdf7df33e28a8137322f9ee67dba8 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Tue, 5 May 2026 23:58:11 +0000 Subject: [PATCH 08/11] adjust the ic metrics description --- rs/ai_agent/src/tools/ic_metrics.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rs/ai_agent/src/tools/ic_metrics.rs b/rs/ai_agent/src/tools/ic_metrics.rs index 4d39278b398a..6bff6d43d320 100644 --- a/rs/ai_agent/src/tools/ic_metrics.rs +++ b/rs/ai_agent/src/tools/ic_metrics.rs @@ -631,7 +631,15 @@ impl Tool for IcMetrics { to narrow), `get` returns current samples, `summary` returns a curated \ dashboard for the chosen source, `rate` computes a per-second delta \ against the previous call. Targets a specific peer node by `node_id` \ - (resolved via the local registry) or by raw `ipv6`." + (resolved via the local registry) or by raw `ipv6`.\n\ + \n\ + IMPORTANT: do NOT query the local node (this AI node). It is a passive \ + state-sync observer, not an active subnet member, so its metrics are \ + not representative of subnet health — replica counters will be near \ + zero, consensus metrics will be missing entirely, and node_exporter \ + will reflect only this AI node's own host. Always pick a `node_id` \ + of an active consensus member (use `ic_state` op=`subnet` to list \ + them and copy a node id from the `nodes` array)." .to_string(), parameters: json!({ "type": "object", From ec28892654f94b2778aa309082227f4799664147 Mon Sep 17 00:00:00 2001 From: IDX GitHub Automation Date: Wed, 6 May 2026 00:05:55 +0000 Subject: [PATCH 09/11] Automatically fixing code for linting and formatting issues --- bazel/rust.MODULE.bazel | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bazel/rust.MODULE.bazel b/bazel/rust.MODULE.bazel index a71d1fd56b46..aebc3a04d116 100644 --- a/bazel/rust.MODULE.bazel +++ b/bazel/rust.MODULE.bazel @@ -2116,6 +2116,7 @@ crate.annotation( "__tls", "rustls-no-provider", ], + version = "0.13.3", deps = [ "@crate_index__hyper-rustls-0.27.7//:hyper_rustls", "@crate_index__rustls-0.23.27//:rustls", @@ -2123,10 +2124,7 @@ crate.annotation( "@crate_index__rustls-platform-verifier-0.6.2//:rustls_platform_verifier", "@crate_index__tokio-rustls-0.26.0//:tokio_rustls", ], - version = "0.13.3", ) - - crate.annotation( crate = "curve25519-dalek", rustc_flags = [ From 4c2b5ba93fdd011261cb074ba45744f2e7ab088d Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Wed, 6 May 2026 00:37:42 +0000 Subject: [PATCH 10/11] linting --- rs/ai_agent/src/handlers/chat.rs | 34 +++++++++++++++++++++--------- rs/ai_agent/src/handlers/config.rs | 18 ++++++++++------ rs/ai_agent/src/handlers/health.rs | 9 ++++++-- rs/ai_agent/src/handlers/run.rs | 33 ++++++++++++++++++++--------- rs/ai_agent/src/models/request.rs | 11 +++++----- rs/ai_agent/src/sessions.rs | 7 ++++++ rs/ai_agent/src/state.rs | 10 +++++++-- rs/ai_agent/src/tools/ic_state.rs | 2 +- 8 files changed, 87 insertions(+), 37 deletions(-) diff --git a/rs/ai_agent/src/handlers/chat.rs b/rs/ai_agent/src/handlers/chat.rs index 6459224f4367..eeba41639959 100644 --- a/rs/ai_agent/src/handlers/chat.rs +++ b/rs/ai_agent/src/handlers/chat.rs @@ -47,9 +47,9 @@ pub async fn chat( } // Resolve the provider once for this request. - let provider = match read_provider(&state).await { + let provider = match read_provider(&state) { Ok(p) => p, - Err(resp) => return resp, + Err(resp) => return *resp, }; // Pull the cached transcript (if any). The snapshot is owned — @@ -152,15 +152,29 @@ pub async fn chat( } /// Read the active provider, returning a 503 response if `/v1/config` -/// hasn't been called yet. -async fn read_provider(state: &Arc) -> Result { - let guard = state.provider.read().await; +/// hasn't been called yet (or the lock is poisoned, which would mean +/// a previous `/v1/config` write panicked mid-update — best handled +/// the same way as "not configured"). +/// +/// The error variant is boxed so the resulting `Result` stays small; +/// `axum::response::Response` is ~128 bytes and `clippy::result_large_err` +/// otherwise complains. +fn read_provider(state: &Arc) -> Result> { + let guard = match state.provider.read() { + Ok(g) => g, + Err(_) => return Err(Box::new(provider_unavailable())), + }; match guard.as_ref() { Some(p) => Ok(p.clone()), - None => Err(( - StatusCode::SERVICE_UNAVAILABLE, - Json(ErrorBody::new(provider_not_configured().to_string())), - ) - .into_response()), + None => Err(Box::new(provider_unavailable())), } } + +/// Build the 503 "provider not configured" response. +fn provider_unavailable() -> axum::response::Response { + ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ErrorBody::new(provider_not_configured().to_string())), + ) + .into_response() +} diff --git a/rs/ai_agent/src/handlers/config.rs b/rs/ai_agent/src/handlers/config.rs index c793dee30123..ffd960784e62 100644 --- a/rs/ai_agent/src/handlers/config.rs +++ b/rs/ai_agent/src/handlers/config.rs @@ -25,12 +25,18 @@ pub async fn configure( provider: provider.name().to_string(), model: provider.model().to_string(), }; - *state.provider.write().await = Some(provider); - // Drop all cached chat sessions: their Agents are bound - // to whichever GeminiClient (= API key) was active when - // they were built. After a re-config they would silently - // keep using the previous credentials, which is exactly - // the wrong behaviour after a key rotation. + // Sync RwLock; we don't `.await` while the guard is alive. + // A poisoned lock means a prior writer panicked — recover + // by overwriting the inner value, which is what the + // `into_inner()` recovery pattern would do anyway. + match state.provider.write() { + Ok(mut guard) => *guard = Some(provider), + Err(poisoned) => *poisoned.into_inner() = Some(provider), + } + // Drop all cached chat sessions: their cached transcripts + // were produced under the previous model/credential. Mixing + // them across a reconfiguration is more confusing than + // helpful, especially for key rotations. let cleared = state.sessions.clear(); info!( state.log, diff --git a/rs/ai_agent/src/handlers/health.rs b/rs/ai_agent/src/handlers/health.rs index e2d43f749d1d..2bf3891f6410 100644 --- a/rs/ai_agent/src/handlers/health.rs +++ b/rs/ai_agent/src/handlers/health.rs @@ -6,8 +6,13 @@ use crate::{models::HealthResponse, state::AppState}; /// `GET /v1/health` — liveness probe; reports active provider/model if any. pub async fn health(State(state): State>) -> Json { - let provider = state.provider.read().await; - let (provider_name, model) = match provider.as_ref() { + // A poisoned provider lock can only happen if a panic occurred + // while it was held write-locked, which would mean the process + // is in a degraded state already; in that case it's still + // sensible to report "ok" with no provider so liveness probes + // can detect the degraded state via the missing fields. + let provider = state.provider.read().ok(); + let (provider_name, model) = match provider.as_deref().and_then(|opt| opt.as_ref()) { Some(p) => (Some(p.name().to_string()), Some(p.model().to_string())), None => (None, None), }; diff --git a/rs/ai_agent/src/handlers/run.rs b/rs/ai_agent/src/handlers/run.rs index d3a27889b2f5..7487c705b12e 100644 --- a/rs/ai_agent/src/handlers/run.rs +++ b/rs/ai_agent/src/handlers/run.rs @@ -42,18 +42,31 @@ pub async fn run( .into_response(); } - let provider_guard = state.provider.read().await; - let provider = match provider_guard.as_ref() { - Some(p) => p.clone(), - None => { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ErrorBody::new(provider_not_configured().to_string())), - ) - .into_response(); + // `state.provider` is a sync RwLock; we never hold it across an + // `.await`. Clone the active provider out and drop the guard + // immediately by going out of scope at the end of this block. + let provider = { + let provider_guard = match state.provider.read() { + Ok(g) => g, + Err(_) => { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ErrorBody::new(provider_not_configured().to_string())), + ) + .into_response(); + } + }; + match provider_guard.as_ref() { + Some(p) => p.clone(), + None => { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ErrorBody::new(provider_not_configured().to_string())), + ) + .into_response(); + } } }; - drop(provider_guard); let preamble = req .preamble diff --git a/rs/ai_agent/src/models/request.rs b/rs/ai_agent/src/models/request.rs index 03d82a2e87f2..db720dc84716 100644 --- a/rs/ai_agent/src/models/request.rs +++ b/rs/ai_agent/src/models/request.rs @@ -17,13 +17,12 @@ pub struct RunRequest { /// The session_id is what makes this multi-turn: omit it on the first /// call to start a new session (the server returns the freshly-minted /// id), then echo the same id on every subsequent turn. The server -/// caches one configured Agent per session id and only the new user -/// prompt needs to be sent each time. +/// caches the conversation transcript per session id; only the new +/// user prompt needs to be sent each time. /// -/// `preamble`, `tools`, and `max_turns` are honoured only at session- -/// creation time. They are ignored on subsequent turns of an existing -/// session — the cached Agent's configuration wins. To change them, -/// start a new session. +/// `preamble`, `tools`, and `max_turns` apply per-request — the agent +/// is rebuilt each turn from the cached transcript, so changing them +/// between turns simply changes how the next turn is run. #[derive(Debug, Deserialize)] pub struct ChatRequest { pub prompt: String, diff --git a/rs/ai_agent/src/sessions.rs b/rs/ai_agent/src/sessions.rs index 8bb4f5f2af30..bcbbe0c5c908 100644 --- a/rs/ai_agent/src/sessions.rs +++ b/rs/ai_agent/src/sessions.rs @@ -173,6 +173,13 @@ impl SessionStore { pub fn len(&self) -> usize { self.inner.lock().unwrap().len() } + + /// Whether the cache currently has no sessions. Paired with + /// [`SessionStore::len`] to satisfy `clippy::len_without_is_empty`. + #[allow(dead_code)] + pub fn is_empty(&self) -> bool { + self.inner.lock().unwrap().is_empty() + } } /// Read snapshot of a session. The transcript is owned (cloned out diff --git a/rs/ai_agent/src/state.rs b/rs/ai_agent/src/state.rs index eddcb516a180..5622082bd7e3 100644 --- a/rs/ai_agent/src/state.rs +++ b/rs/ai_agent/src/state.rs @@ -11,14 +11,20 @@ use crate::{ tools::node_directory::{NodeDirectory, NodeDirectoryError}, }; use slog::Logger; -use std::sync::Arc; -use tokio::sync::{OnceCell, RwLock}; +use std::sync::{Arc, RwLock}; +use tokio::sync::OnceCell; /// Mutable runtime state shared across handlers. pub struct AppState { pub config: AppConfig, pub log: Logger, /// `None` until `POST /v1/config` populates it. + /// + /// `std::sync::RwLock` (not the tokio variant): every call site + /// only holds the guard long enough to clone an `AiProvider` (a + /// cheap struct of `String` + `GeminiClient`) or to write a new + /// one in. None of them `.await` while the guard is alive, which + /// is the criterion for using a blocking lock under tokio. pub provider: RwLock>, /// Lazily constructed registry-backed node lookup. Built on first /// use because (a) it touches the filesystem and we want diff --git a/rs/ai_agent/src/tools/ic_state.rs b/rs/ai_agent/src/tools/ic_state.rs index e137cb909ff2..e4b05098ab15 100644 --- a/rs/ai_agent/src/tools/ic_state.rs +++ b/rs/ai_agent/src/tools/ic_state.rs @@ -417,7 +417,7 @@ impl HasRaw for ic_protobuf::types::v1::CanisterId { /// Decode a little-endian byte sequence (up to 16 bytes) into a u128. /// `Cycles.raw_cycles` is the LE encoding of the underlying u128. fn u128_from_le_bytes(bytes: &[u8]) -> u128 { - let mut buf = [0u8; 16]; + let mut buf = [0_u8; 16]; let n = bytes.len().min(16); buf[..n].copy_from_slice(&bytes[..n]); u128::from_le_bytes(buf) From 388614848f2be4a2e7b8cadaab03c87ef1ed7e2f Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Wed, 6 May 2026 09:20:40 +0000 Subject: [PATCH 11/11] fixing golden rules --- .../nftables_assigned_cloud_engine.conf.golden | 14 ++++++++++---- .../testdata/nftables_assigned_replica.conf.golden | 14 ++++++++++---- .../nftables_boundary_node_app_subnet.conf.golden | 2 ++ ...ftables_boundary_node_system_subnet.conf.golden | 2 ++ .../nftables_unassigned_cloud_engine.conf.golden | 14 ++++++++++---- .../nftables_unassigned_replica.conf.golden | 14 ++++++++++---- 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/rs/orchestrator/testdata/nftables_assigned_cloud_engine.conf.golden b/rs/orchestrator/testdata/nftables_assigned_cloud_engine.conf.golden index 6a6a4142535b..5d0feb0e3c56 100644 --- a/rs/orchestrator/testdata/nftables_assigned_cloud_engine.conf.golden +++ b/rs/orchestrator/testdata/nftables_assigned_cloud_engine.conf.golden @@ -49,9 +49,11 @@ ip saddr {6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 reject below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 reject below; nftables + # evaluates top-down, so the accepts short-circuit the reject. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter ip daddr { 127.0.0.0/8 } ct state { new } tcp dport { 1-19999 } reject # Block restricted localhost ic-http-adapter HTTPS access meta skuid ic-http-adapter ip daddr {1.1.1.1,3.0.0.3,3.0.0.4,3.0.0.5,3.0.0.6,3.0.0.7,4.0.0.4,4.0.0.5,4.0.0.6,4.0.0.7} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter } @@ -103,6 +105,8 @@ table ip6 filter { ip6 saddr { hostos } ct state { new } tcp dport { 42372 } accept # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama). ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes). + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept # Custom templated rules ip6 saddr {2001:db8:85a3::8a2e:1370:7334,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,8080} accept # Automatic node whitelisting ip6 saddr {::ffff:5.5.5.5} ct state { new } tcp dport {1005} accept # node_gwp4o-eaaaa-aaaaa-aaaap-2ai @@ -120,9 +124,11 @@ ip6 saddr {::ffff:6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 rejects below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 rejects below; nftables + # evaluates top-down, so the accepts short-circuit the rejects. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter fib daddr type local ct state { new } tcp dport { 1-19999 } reject # Block restricted local addresses ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr { 2a00:fb01:400:42::/64, 2602:fb2b:110::/48, 2602:fb2b:100::/48, 2602:fb2b:120::/48 } ct state { new } tcp dport { 1-19999 } reject # Block restricted outbound ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr {2001:db8:85a3::8a2e:1370:7334,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter diff --git a/rs/orchestrator/testdata/nftables_assigned_replica.conf.golden b/rs/orchestrator/testdata/nftables_assigned_replica.conf.golden index b9b0f8fdaa9c..00b3a771f5fd 100644 --- a/rs/orchestrator/testdata/nftables_assigned_replica.conf.golden +++ b/rs/orchestrator/testdata/nftables_assigned_replica.conf.golden @@ -49,9 +49,11 @@ ip saddr {6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 reject below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 reject below; nftables + # evaluates top-down, so the accepts short-circuit the reject. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter ip daddr { 127.0.0.0/8 } ct state { new } tcp dport { 1-19999 } reject # Block restricted localhost ic-http-adapter HTTPS access meta skuid ic-http-adapter ip daddr {1.1.1.1,3.0.0.3,3.0.0.4,3.0.0.5,3.0.0.6,3.0.0.7,4.0.0.4,4.0.0.5,4.0.0.6,4.0.0.7} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter } @@ -103,6 +105,8 @@ table ip6 filter { ip6 saddr { hostos } ct state { new } tcp dport { 42372 } accept # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama). ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes). + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept # Custom templated rules ip6 saddr {3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,8080} accept # Automatic node whitelisting ip6 saddr {::ffff:5.5.5.5} ct state { new } tcp dport {1005} accept # node_gwp4o-eaaaa-aaaaa-aaaap-2ai @@ -120,9 +124,11 @@ ip6 saddr {::ffff:6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 rejects below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 rejects below; nftables + # evaluates top-down, so the accepts short-circuit the rejects. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter fib daddr type local ct state { new } tcp dport { 1-19999 } reject # Block restricted local addresses ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr { 2a00:fb01:400:42::/64, 2602:fb2b:110::/48, 2602:fb2b:100::/48, 2602:fb2b:120::/48 } ct state { new } tcp dport { 1-19999 } reject # Block restricted outbound ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr {2001:db8:85a3::8a2e:1370:7334,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter diff --git a/rs/orchestrator/testdata/nftables_boundary_node_app_subnet.conf.golden b/rs/orchestrator/testdata/nftables_boundary_node_app_subnet.conf.golden index 95abe290f10c..83c52e8d743d 100644 --- a/rs/orchestrator/testdata/nftables_boundary_node_app_subnet.conf.golden +++ b/rs/orchestrator/testdata/nftables_boundary_node_app_subnet.conf.golden @@ -91,6 +91,8 @@ table ip6 filter { ip6 saddr { ::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff } ct state new tcp dport 443 accept # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama). ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes). + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept ip6 saddr {3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7} ct state { new } tcp dport {1080} accept # nodes for SOCKS proxy ip6 saddr {::ffff:5.5.5.5} ct state { new } tcp dport {1005} accept # node_gol4s-2gsaq-aaaaa-aaaap-2ai diff --git a/rs/orchestrator/testdata/nftables_boundary_node_system_subnet.conf.golden b/rs/orchestrator/testdata/nftables_boundary_node_system_subnet.conf.golden index 3a6e6d51ff2b..375e0fbce30a 100644 --- a/rs/orchestrator/testdata/nftables_boundary_node_system_subnet.conf.golden +++ b/rs/orchestrator/testdata/nftables_boundary_node_system_subnet.conf.golden @@ -91,6 +91,8 @@ table ip6 filter { ip6 saddr { ::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff } ct state new tcp dport 443 accept # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama). ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes). + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept ip6 saddr {a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {1080} accept # nodes for SOCKS proxy ip6 saddr {::ffff:5.5.5.5} ct state { new } tcp dport {1005} accept # node_gwp4o-eaaaa-aaaaa-aaaap-2ai diff --git a/rs/orchestrator/testdata/nftables_unassigned_cloud_engine.conf.golden b/rs/orchestrator/testdata/nftables_unassigned_cloud_engine.conf.golden index ce84b669dd8e..545965b68bf9 100644 --- a/rs/orchestrator/testdata/nftables_unassigned_cloud_engine.conf.golden +++ b/rs/orchestrator/testdata/nftables_unassigned_cloud_engine.conf.golden @@ -48,9 +48,11 @@ ip saddr {6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 reject below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 reject below; nftables + # evaluates top-down, so the accepts short-circuit the reject. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter ip daddr { 127.0.0.0/8 } ct state { new } tcp dport { 1-19999 } reject # Block restricted localhost ic-http-adapter HTTPS access meta skuid ic-http-adapter ip daddr {1.1.1.1,3.0.0.3,3.0.0.4,3.0.0.5,3.0.0.6,3.0.0.7,4.0.0.4,4.0.0.5,4.0.0.6,4.0.0.7} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter } @@ -102,6 +104,8 @@ table ip6 filter { ip6 saddr { hostos } ct state { new } tcp dport { 42372 } accept # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama). ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes). + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept # Custom templated rules ip6 saddr {2001:db8:85a3::8a2e:1370:7334,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,8080} accept # Automatic node whitelisting ip6 saddr {::ffff:5.5.5.5} ct state { new } tcp dport {1005} accept # node_gwp4o-eaaaa-aaaaa-aaaap-2ai @@ -118,9 +122,11 @@ ip6 saddr {::ffff:6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 rejects below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 rejects below; nftables + # evaluates top-down, so the accepts short-circuit the rejects. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter fib daddr type local ct state { new } tcp dport { 1-19999 } reject # Block restricted local addresses ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr { 2a00:fb01:400:42::/64, 2602:fb2b:110::/48, 2602:fb2b:100::/48, 2602:fb2b:120::/48 } ct state { new } tcp dport { 1-19999 } reject # Block restricted outbound ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr {2001:db8:85a3::8a2e:1370:7334,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter diff --git a/rs/orchestrator/testdata/nftables_unassigned_replica.conf.golden b/rs/orchestrator/testdata/nftables_unassigned_replica.conf.golden index 7bd60a3dcb27..6c90bdd67189 100644 --- a/rs/orchestrator/testdata/nftables_unassigned_replica.conf.golden +++ b/rs/orchestrator/testdata/nftables_unassigned_replica.conf.golden @@ -48,9 +48,11 @@ ip saddr {6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 reject below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 reject below; nftables + # evaluates top-down, so the accepts short-circuit the reject. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter ip daddr { 127.0.0.0/8 } ct state { new } tcp dport { 1-19999 } reject # Block restricted localhost ic-http-adapter HTTPS access meta skuid ic-http-adapter ip daddr {1.1.1.1,3.0.0.3,3.0.0.4,3.0.0.5,3.0.0.6,3.0.0.7,4.0.0.4,4.0.0.5,4.0.0.6,4.0.0.7} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter } @@ -102,6 +104,8 @@ table ip6 filter { ip6 saddr { hostos } ct state { new } tcp dport { 42372 } accept # Allow access to the ollama HTTP API (disabled by default, started on-demand via systemctl enable --now ollama). ip6 daddr { ::/0 } ct state { new } tcp dport { 11434 } accept + # Allow access to the IC AI agent HTTP API (disabled by default, started on-demand on AI nodes). + ip6 daddr { ::/0 } ct state { new } tcp dport { 11500 } accept # Custom templated rules ip6 saddr {3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,8080} accept # Automatic node whitelisting ip6 saddr {::ffff:5.5.5.5} ct state { new } tcp dport {1005} accept # node_gwp4o-eaaaa-aaaaa-aaaap-2ai @@ -118,9 +122,11 @@ ip6 saddr {::ffff:6.6.6.6} ct state { new } tcp dport {1006} accept # global chain OUTPUT { type filter hook output priority 0; policy accept; # Allow ic-http-adapter to reach the ollama TLS reverse proxy on port - # 11434. This must come before the blanket 1-19999 rejects below; - # nftables evaluates top-down, so the accept short-circuits the reject. + # 11434, and the IC AI agent TLS reverse proxy on port 11500. These + # accepts must come before the blanket 1-19999 rejects below; nftables + # evaluates top-down, so the accepts short-circuit the rejects. meta skuid ic-http-adapter ct state { new } tcp dport { 11434 } accept # Allow ic-http-adapter outbound to ollama (port 11434) + meta skuid ic-http-adapter ct state { new } tcp dport { 11500 } accept # Allow ic-http-adapter outbound to ic-ai-agent (port 11500) meta skuid ic-http-adapter fib daddr type local ct state { new } tcp dport { 1-19999 } reject # Block restricted local addresses ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr { 2a00:fb01:400:42::/64, 2602:fb2b:110::/48, 2602:fb2b:100::/48, 2602:fb2b:120::/48 } ct state { new } tcp dport { 1-19999 } reject # Block restricted outbound ic-http-adapter HTTPS access meta skuid ic-http-adapter ip6 daddr {2001:db8:85a3::8a2e:1370:7334,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e5,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e6,3fda:92b7:4c1e:8a23:7d61:2f9c:ab42:19e7,a4c2:7f91:3db6:1e8c:5a4f:cc92:b37:6e41} ct state { new } tcp dport {22,2497,4100,7070,8080,9090,9091,9100,19100,19523,19531} reject # Automatic blacklisting for ic-http-adapter