From 9a01a8baa3f6d7d64ad6b69986f3ba1c560c434e Mon Sep 17 00:00:00 2001 From: 0xZensh Date: Sat, 11 Jan 2025 23:56:49 +0800 Subject: [PATCH] feat: implement `anda_engine` --- .gitignore | 3 +- Cargo.lock | 2131 ++++++++++++++++++++++++++++-- Cargo.toml | 6 +- anda_core/Cargo.toml | 5 +- anda_core/README.md | 2 +- anda_core/src/agent.rs | 259 ++-- anda_core/src/context.rs | 309 +++-- anda_core/src/http.rs | 214 +++ anda_core/src/lib.rs | 35 +- anda_core/src/tool.rs | 135 +- anda_engine/Cargo.toml | 35 + anda_engine/README.md | 22 + anda_engine/src/context/agent.rs | 478 +++++++ anda_engine/src/context/base.rs | 506 +++++++ anda_engine/src/context/cache.rs | 139 ++ anda_engine/src/context/keys.rs | 158 +++ anda_engine/src/context/mod.rs | 9 + anda_engine/src/engine.rs | 118 ++ anda_engine/src/lib.rs | 2 + 19 files changed, 4266 insertions(+), 300 deletions(-) create mode 100644 anda_core/src/http.rs create mode 100644 anda_engine/Cargo.toml create mode 100644 anda_engine/README.md create mode 100644 anda_engine/src/context/agent.rs create mode 100644 anda_engine/src/context/base.rs create mode 100644 anda_engine/src/context/cache.rs create mode 100644 anda_engine/src/context/keys.rs create mode 100644 anda_engine/src/context/mod.rs create mode 100644 anda_engine/src/engine.rs create mode 100644 anda_engine/src/lib.rs diff --git a/.gitignore b/.gitignore index 38be5e3..3f9fc99 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ target/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ +debug \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8d09973..4a739f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,20 +52,75 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anda_core" +version = "0.2.0" +dependencies = [ + "bytes", + "candid", + "ciborium", + "futures", + "http", + "ic_cose_types", + "object_store", + "reqwest", + "serde", + "serde_bytes", + "serde_json", + "thiserror 2.0.10", + "tokio-util", +] + +[[package]] +name = "anda_engine" version = "0.1.0" dependencies = [ + "anda_core", "bytes", "candid", "ciborium", + "const-hex", "futures", + "futures-util", "http", + "ic_cose", "ic_cose_types", + "ic_object_store", + "ic_tee_agent", + "moka", "object_store", + "reqwest", "serde", "serde_bytes", "serde_json", + "structured-logger", "thiserror 2.0.10", "tokio-util", ] @@ -91,12 +146,53 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-compression" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-trait" version = "0.1.85" @@ -108,12 +204,105 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "async-watch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a078faf4e27c0c6cc0efb20e5da59dcccc04968ebf2801d8e0b2195124cdcdb2" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -141,6 +330,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -176,6 +371,15 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -203,6 +407,19 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "cached" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8466736fe5dbcaf8b8ee24f9bbefe43c884dc3e9ff7178da70f55bffca1133c" +dependencies = [ + "ahash", + "hashbrown 0.14.5", + "instant", + "once_cell", + "thiserror 1.0.69", +] + [[package]] name = "candid" version = "0.10.11" @@ -253,6 +470,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.39" @@ -289,7 +512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half", + "half 2.4.1", ] [[package]] @@ -302,6 +525,15 @@ dependencies = [ "inout", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-hex" version = "1.14.0" @@ -321,6 +553,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -355,6 +607,30 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crunchy" version = "0.2.2" @@ -402,7 +678,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -420,6 +696,19 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core", + "subtle-ng", + "zeroize", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -433,6 +722,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -445,13 +735,22 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -475,7 +774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -492,6 +791,21 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core", + "serde", + "sha2 0.9.9", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "ed25519-dalek" version = "2.1.1" @@ -501,7 +815,7 @@ dependencies = [ "curve25519-dalek", "ed25519", "serde", - "sha2", + "sha2 0.10.8", "subtle", "zeroize", ] @@ -520,10 +834,11 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core", "sec1", @@ -531,6 +846,74 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "ff" version = "0.13.0" @@ -547,12 +930,37 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -652,8 +1060,21 @@ dependencies = [ ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ @@ -669,8 +1090,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -700,6 +1123,31 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "half" version = "2.4.1" @@ -710,6 +1158,22 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.5.0" @@ -740,7 +1204,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -754,12 +1218,122 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -771,7 +1345,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -783,6 +1357,86 @@ dependencies = [ "cc", ] +[[package]] +name = "ic-agent" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba408987ca48fc3eee6a613e760d076a9046cccbbb5ba29efbada339ab28ed9" +dependencies = [ + "arc-swap", + "async-channel", + "async-lock", + "async-trait", + "async-watch", + "backoff", + "cached", + "candid", + "der", + "ecdsa", + "ed25519-consensus", + "elliptic-curve", + "futures-util", + "hex", + "http", + "http-body", + "ic-certification 3.0.2", + "ic-transport-types", + "ic-verify-bls-signature", + "k256", + "leb128", + "p256", + "pem", + "pkcs8", + "rand", + "rangemap", + "reqwest", + "sec1", + "serde", + "serde_bytes", + "serde_cbor", + "serde_repr", + "sha2 0.10.8", + "simple_asn1", + "stop-token", + "thiserror 2.0.10", + "time", + "tokio", + "tower-service", + "url", +] + +[[package]] +name = "ic-canister-sig-creation" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db33deb06e0edb366d8d86ef67d7bc1e1759bc7046b0323a33b85b21b8d8d87" +dependencies = [ + "candid", + "hex", + "ic-cdk 0.14.1", + "ic-certification 2.6.0", + "ic-representation-independent-hash", + "lazy_static", + "serde", + "serde_bytes", + "serde_cbor", + "sha2 0.10.8", + "thiserror 1.0.69", +] + +[[package]] +name = "ic-cdk" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cff1a3c3db565e3384c9c9d6d676b0a3f89a0886f4f787294d9c946d844369f" +dependencies = [ + "candid", + "ic-cdk-macros 0.14.0", + "ic0", + "serde", + "serde_bytes", +] + [[package]] name = "ic-cdk" version = "0.17.1" @@ -790,12 +1444,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "122efbcb0af5280d408a75a57b7dc6e9d92893bf6ed9cc98fe4dcff51f18b67c" dependencies = [ "candid", - "ic-cdk-macros", + "ic-cdk-macros 0.17.1", "ic0", "serde", "serde_bytes", ] +[[package]] +name = "ic-cdk-macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01dc6bc425ec048d6ac4137c7c0f2cfbd6f8b0be8efc568feae2b265f566117c" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream", + "syn 2.0.95", +] + [[package]] name = "ic-cdk-macros" version = "0.17.1" @@ -810,12 +1478,106 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "ic-certification" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64ee3d8b6e81b51f245716d3e0badb63c283c00f3c9fb5d5219afc30b5bf821" +dependencies = [ + "hex", + "serde", + "serde_bytes", + "sha2 0.10.8", +] + +[[package]] +name = "ic-certification" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eae40f26fcac9c141cad54d9aa5f423efffde78ac371057c53d275ebbcad443" +dependencies = [ + "hex", + "serde", + "serde_bytes", + "sha2 0.10.8", +] + +[[package]] +name = "ic-representation-independent-hash" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ae59483e377cd9aad94ec339ed1d2583b0d5929cab989328dac2d853b2f570" +dependencies = [ + "leb128", + "sha2 0.10.8", +] + +[[package]] +name = "ic-transport-types" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e2418868dd5857d2a5bac3f1cb6de1aecf2316d380997ef842aec3d8a79d4e" +dependencies = [ + "candid", + "hex", + "ic-certification 3.0.2", + "leb128", + "serde", + "serde_bytes", + "serde_cbor", + "serde_repr", + "sha2 0.10.8", + "thiserror 2.0.10", +] + +[[package]] +name = "ic-verify-bls-signature" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d420b25c0091059f6c3c23a21427a81915e6e0aca3b79e0d403ed767f286a3b9" +dependencies = [ + "hex", + "ic_bls12_381", + "lazy_static", + "pairing", + "rand", + "sha2 0.10.8", +] + [[package]] name = "ic0" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +[[package]] +name = "ic_bls12_381" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22c65787944f32af084dffd0c68c1e544237b76e215654ddea8cd9f527dd8b69" +dependencies = [ + "digest 0.10.7", + "ff", + "group", + "pairing", + "rand_core", + "subtle", +] + +[[package]] +name = "ic_cose" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05177c8346afdfff6d2ed0af2cc99d86b542e4c9cb45db0b5a0b30f4264c1db" +dependencies = [ + "candid", + "ic-agent", + "ic_cose_types", + "rand", + "serde_bytes", + "x25519-dalek", +] + [[package]] name = "ic_cose_types" version = "0.6.4" @@ -830,18 +1592,37 @@ dependencies = [ "ed25519-dalek", "hkdf", "hmac", - "ic-cdk", + "ic-cdk 0.17.1", "icrc-ledger-types", "k256", "num-traits", "serde", "serde_bytes", - "sha2", + "sha2 0.10.8", "sha3", "thiserror 2.0.10", "x25519-dalek", ] +[[package]] +name = "ic_object_store" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92238d87caedd5f9fe083c63a571642f83d9b6e448b0ea116b8a428eeab2fe4" +dependencies = [ + "aes-gcm", + "async-trait", + "bytes", + "candid", + "chrono", + "futures", + "ic-agent", + "ic_cose_types", + "object_store", + "rand", + "serde_bytes", +] + [[package]] name = "ic_principal" version = "0.1.1" @@ -851,10 +1632,49 @@ dependencies = [ "crc32fast", "data-encoding", "serde", - "sha2", + "sha2 0.10.8", "thiserror 1.0.69", ] +[[package]] +name = "ic_tee_agent" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cce2389caba3538c4176e4072bb4ffb160d811c567455b4636d318dee23e1ca" +dependencies = [ + "axum", + "base64", + "bytes", + "candid", + "ciborium", + "ed25519-consensus", + "ic-agent", + "ic-canister-sig-creation", + "ic_cose", + "ic_cose_types", + "ic_tee_cdk", + "mime", + "rand", + "serde", + "serde_bytes", + "serde_json", + "thiserror 2.0.10", + "tokio", +] + +[[package]] +name = "ic_tee_cdk" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72c60becf8bba5364d9700bb4e7613ffed16bf72bf5bcadb20fa2e6f5c3d16" +dependencies = [ + "candid", + "ciborium", + "ic-canister-sig-creation", + "serde", + "serde_bytes", +] + [[package]] name = "icrc-ledger-types" version = "0.1.6" @@ -870,7 +1690,7 @@ dependencies = [ "num-traits", "serde", "serde_bytes", - "sha2", + "sha2 0.10.8", "strum", "time", ] @@ -1014,6 +1834,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + [[package]] name = "inout" version = "0.1.3" @@ -1023,6 +1853,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + [[package]] name = "itertools" version = "0.12.1" @@ -1067,7 +1912,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2", + "sha2 0.10.8", "signature", ] @@ -1104,6 +1949,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "litemap" version = "0.7.4" @@ -1121,10 +1972,42 @@ dependencies = [ ] [[package]] -name = "log" -version = "0.4.22" +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "serde", + "value-bag", +] + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" @@ -1132,6 +2015,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.2" @@ -1141,6 +2030,66 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "moka" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener 5.4.0", + "futures-util", + "loom", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1219,6 +2168,83 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1248,6 +2274,25 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1276,6 +2321,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "polyval" version = "0.6.2" @@ -1288,6 +2339,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1314,6 +2371,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -1335,105 +2401,344 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax", + "regex-syntax 0.8.5", "unarray", ] [[package]] -name = "psm" -version = "0.1.24" +name = "psm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +dependencies = [ + "cc", +] + +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.10", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.10", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "cc", + "hmac", + "subtle", ] [[package]] -name = "quote" -version = "1.0.38" +name = "ring" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ - "proc-macro2", + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] -name = "rand" -version = "0.8.5" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rustc-hash" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] -name = "rand_core" -version = "0.6.4" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "getrandom", + "semver", ] [[package]] -name = "rand_xorshift" -version = "0.3.0" +name = "rustix" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ - "rand_core", + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", ] [[package]] -name = "redox_syscall" -version = "0.5.8" +name = "rustls" +version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ - "bitflags", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "regex-syntax" -version = "0.8.5" +name = "rustls-native-certs" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] [[package]] -name = "rfc6979" -version = "0.4.0" +name = "rustls-pemfile" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "hmac", - "subtle", + "rustls-pki-types", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rustls-pki-types" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +dependencies = [ + "web-time", +] [[package]] -name = "rustc_version" -version = "0.4.1" +name = "rustls-webpki" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "semver", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1457,6 +2762,21 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1477,6 +2797,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.24" @@ -1501,6 +2857,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.217" @@ -1512,6 +2878,15 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "serde_fmt" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" +dependencies = [ + "serde", +] + [[package]] name = "serde_json" version = "1.0.135" @@ -1524,6 +2899,27 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + [[package]] name = "serde_tokenstream" version = "0.2.2" @@ -1536,6 +2932,31 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1544,7 +2965,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -1553,26 +2974,56 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 1.0.69", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -1609,6 +3060,22 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -1635,7 +3102,32 @@ dependencies = [ "cfg-if", "libc", "psm", - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "stop-token" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af91f480ee899ab2d9f8435bfdfc14d08a5754bd9d3fef1f1a1c23336aad6c8b" +dependencies = [ + "async-channel", + "cfg-if", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "structured-logger" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16524b1ef57fd2e253216ab20ec44f0dc32b29155a4b3e6bef0a857d8c9f5f08" +dependencies = [ + "log", + "parking_lot", + "serde", + "serde_json", + "tokio", ] [[package]] @@ -1666,6 +3158,90 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "sval" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8" + +[[package]] +name = "sval_buffer" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a" +dependencies = [ + "serde", + "sval", + "sval_nested", +] + [[package]] name = "syn" version = "1.0.109" @@ -1688,6 +3264,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -1699,6 +3284,47 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[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 = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1739,6 +3365,16 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.37" @@ -1780,6 +3416,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.43.0" @@ -1788,8 +3439,14 @@ checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", + "libc", + "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", + "socket2", "tokio-macros", + "windows-sys 0.52.0", ] [[package]] @@ -1803,6 +3460,26 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.13" @@ -1816,12 +3493,41 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1845,14 +3551,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typed-arena" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typenum" version = "1.17.0" @@ -1887,6 +3635,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -1910,6 +3664,63 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "value-bag" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" +dependencies = [ + "erased-serde", + "serde", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1926,6 +3737,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1957,6 +3777,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.99" @@ -1986,13 +3819,87 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets", ] [[package]] @@ -2004,6 +3911,80 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 351d072..aa981b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["anda_core"] +members = ["anda_core", "anda_engine"] [workspace.package] name = "anda" @@ -35,6 +35,7 @@ candid = "0.10" ciborium = "0.2" const-hex = "1" futures = "0.3" +futures-util = "0.3" lazy_static = "1.5" http = "1.2" serde = { version = "1", features = ["derive"] } @@ -42,6 +43,8 @@ serde_json = "1" serde_bytes = "0.11" ic_cose_types = "0.6" ic_cose = "0.6" +ic_object_store = { version = "0.6" } +ic_tee_agent = "0.2" object_store = { version = "0.11" } tokio = { version = "1", features = ["full"] } tokio-util = "0.7" @@ -57,6 +60,7 @@ reqwest = { version = "0.12", features = [ # "hickory-dns", ], default-features = true } thiserror = "2" +moka = { version = "0.12", features = ["future"] } [profile.release] debug = false diff --git a/anda_core/Cargo.toml b/anda_core/Cargo.toml index 3d3ca59..c372d47 100644 --- a/anda_core/Cargo.toml +++ b/anda_core/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "anda_core" description = "Core types and traits for Anda -- a framework for AI agent development." -repository = "https://github.com/ldclabs/anda/tree/main/src/anda_core" +repository = "https://github.com/ldclabs/anda/tree/main/anda_core" publish = true -version = "0.1.0" +version = "0.2.0" edition.workspace = true keywords.workspace = true categories.workspace = true @@ -22,3 +22,4 @@ thiserror = { workspace = true } object_store = { workspace = true } ic_cose_types = { workspace = true } tokio-util = { workspace = true } +reqwest = { workspace = true } diff --git a/anda_core/README.md b/anda_core/README.md index 607068b..d4cb205 100644 --- a/anda_core/README.md +++ b/anda_core/README.md @@ -1,7 +1,7 @@ # `anda_core` [![Crates.io](https://img.shields.io/crates/v/anda_core)](https://crates.io/crates/anda_core) -[![Documentation](https://docs.rs/anda-core/badge.svg)](https://docs.rs/anda_core) +[![Documentation](https://docs.rs/anda_core/badge.svg)](https://docs.rs/anda_core) Core types and traits for Anda. diff --git a/anda_core/src/agent.rs b/anda_core/src/agent.rs index 487033f..b4f0e26 100644 --- a/anda_core/src/agent.rs +++ b/anda_core/src/agent.rs @@ -1,120 +1,191 @@ -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Value; -use std::future::Future; +use std::{collections::BTreeMap, future::Future, marker::PhantomData, pin::Pin}; -use crate::context::BaseContext; +use crate::context::{AgentContext, AgentOutput, FunctionDefinition, Value}; use crate::BoxError; -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct AgentDefinition { - pub name: String, - pub description: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct AgentOutput { - pub message: String, - pub failed_reason: Option, - pub tool_call: Option<(String, Value)>, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Message { - /// "system", "user", or "assistant" - pub role: String, - pub content: String, -} +/// Core trait defining an AI agent's behavior +/// +/// # Type Parameters +/// - `C`: The context type that implements `AgentContext`, must be thread-safe and have a static lifetime +pub trait Agent: Send + Sync +where + C: AgentContext + Send + Sync + 'static, +{ + /// The unique name of the agent. This name should be unique within the engine. + /// + /// # Rules + /// - Must not be empty + /// - Length must be ≤ 64 characters + /// - Can only contain: lowercase letters (a-z), digits (0-9), and underscores (_) + const NAME: &'static str; -#[derive(Clone, Default, Deserialize, Serialize, Debug)] -pub struct Embedding { - pub text: String, - /// The embedding vector - pub vec: Vec, -} + /// Returns the agent's name as a String + fn name(&self) -> String { + Self::NAME.to_string() + } -pub trait AgentContext: BaseContext { - fn completion( + /// Returns the agent's function definition for API integration + /// + /// # Returns + /// - `FunctionDefinition`: The structured definition of the agent's capabilities + fn definition(&self) -> FunctionDefinition; + + /// Executes the agent's main logic with given context and inputs + /// + /// # Arguments + /// - `ctx`: The execution context implementing `AgentContext` + /// - `prompt`: The input prompt or message for the agent + /// - `attachment`: Optional additional data in JSON format + /// + /// # Returns + /// - A future resolving to `Result` + fn run( &self, + ctx: &C, prompt: &str, - json_output: bool, - chat_history: &[Message], - ) -> impl Future> + Send; + attachment: Option, + ) -> impl Future> + Send + Sync; +} - fn tool_call( - &self, - tool_name: &str, - args: &Value, - ) -> impl Future> + Send; +/// Dynamic dispatch version of Agent trait for runtime flexibility +/// +/// This trait allows for runtime polymorphism of agents, enabling dynamic agent selection +/// and execution without knowing the concrete type at compile time. +pub trait AgentDyn: Send + Sync +where + C: AgentContext + Send + Sync + 'static, +{ + fn name(&self) -> String; - fn remote_tool_call( - &self, - endpoint: &str, - tool_name: &str, - args: &Value, - ) -> impl Future> + Send; + fn definition(&self) -> FunctionDefinition; - fn embed( - &self, - texts: impl IntoIterator + Send, - ) -> impl Future, Self::Error>> + Send; + fn run<'a>( + &'a self, + ctx: &'a C, + prompt: &'a str, + attachment: Option, + ) -> Pin> + Send + 'a>>; +} - fn embed_query( - &self, - text: &str, - ) -> impl Future> + Send; +/// Adapter for converting static Agent to dynamic dispatch +struct AgentWrapper(T, PhantomData C>) +where + T: Agent + 'static, + C: AgentContext + Send + Sync + 'static; - /// Get the top n documents based on the distance to the given query. - /// The result is a list of tuples of the form (score, id, document) - fn top_n( - &self, - query: &str, - n: usize, - ) -> impl Future, Self::Error>> + Send - where - T: DeserializeOwned; +impl AgentDyn for AgentWrapper +where + T: Agent + 'static, + C: AgentContext + Send + Sync + 'static, +{ + fn name(&self) -> String { + self.0.name() + } - /// Same as `top_n` but returns the document ids only. - fn top_n_ids( - &self, - query: &str, - n: usize, - ) -> impl std::future::Future, Self::Error>> + Send; + fn definition(&self) -> FunctionDefinition { + self.0.definition() + } - fn agent_run( - &self, - agent_name: &str, - prompt: &str, + fn run<'a>( + &'a self, + ctx: &'a C, + prompt: &'a str, attachment: Option, - ) -> impl Future> + Send; + ) -> Pin> + Send + 'a>> { + Box::pin(self.0.run(ctx, prompt, attachment)) + } +} - fn remote_agent_run( - &self, - endpoint: &str, - agent_name: &str, - prompt: &str, - attachment: Option, - ) -> impl Future> + Send; +/// Collection of registered agents with lookup and execution capabilities +/// +/// # Type Parameters +/// - `C`: The context type that implements `AgentContext` +#[derive(Default)] +pub struct AgentSet { + pub set: BTreeMap>>, } -pub trait Agent: Send + Sync +impl AgentSet where C: AgentContext + Send + Sync + 'static, { - /// The name of the agent. This name should be unique in the engine. - const NAME: &'static str; + /// Creates a new empty AgentSet + pub fn new() -> Self { + Self { + set: BTreeMap::new(), + } + } - fn name(&self) -> String { - Self::NAME.to_string() + /// Checks if an agent with given name exists + pub fn contains(&self, name: &str) -> bool { + self.set.contains_key(name) } - /// A method returning the agent definition. The user prompt can be used to - /// tailor the definition to the specific use case. - fn definition(&self, prompt: Option<&str>) -> AgentDefinition; + /// Retrieves definition for a specific agent + pub fn definition(&self, name: &str) -> Option { + self.set.get(name).map(|tool| tool.definition()) + } - fn run( - &self, - ctx: &C, - prompt: &str, - attachment: Option<&Value>, - ) -> impl Future> + Send + Sync; + /// Returns definitions for all or specified agents + /// + /// # Arguments + /// - `names`: Optional slice of agent names to filter by + /// + /// # Returns + /// - `Vec`: Vector of agent definitions + pub fn definitions(&self, names: Option<&[&str]>) -> Vec { + let names = names.unwrap_or_default(); + self.set + .iter() + .filter_map(|(name, tool)| { + if names.is_empty() || names.contains(&name.as_str()) { + Some(tool.definition()) + } else { + None + } + }) + .collect() + } + + /// Registers a new agent in the set + /// + /// # Arguments + /// - `agent`: The agent to register, must implement `Agent` + pub fn add(&mut self, agent: T) + where + T: Agent + 'static, + { + let agent_dyn = AgentWrapper(agent, PhantomData); + self.set.insert(T::NAME.to_string(), Box::new(agent_dyn)); + } + + /// Executes a specific agent with given parameters + /// + /// # Arguments + /// - `name`: The name of the agent to execute + /// - `ctx`: The execution context + /// - `prompt`: The input prompt + /// - `attachment`: Optional additional data + /// + /// # Returns + /// - A boxed future resolving to `Result` + /// + /// # Errors + /// - Returns an error if the agent is not found + pub fn run<'a>( + &'a self, + name: &str, + ctx: &'a C, + prompt: &'a str, + attachment: Option, + ) -> Pin> + Send + 'a>> { + if let Some(agent) = self.set.get(name) { + agent.run(ctx, prompt, attachment) + } else { + Box::pin(futures::future::ready(Err(format!( + "agent {name} not found" + ) + .into()))) + } + } } diff --git a/anda_core/src/context.rs b/anda_core/src/context.rs index 93be366..1ce3904 100644 --- a/anda_core/src/context.rs +++ b/anda_core/src/context.rs @@ -1,35 +1,57 @@ -use candid::{utils::ArgumentEncoder, CandidType, Principal}; -use ic_cose_types::types::object_store::{GetResult, ObjectMeta, PutMode}; -use object_store::path::Path; -use serde::{de::DeserializeOwned, Serialize}; -use std::{ - future::Future, - ops::{Deref, DerefMut}, - time::Duration, -}; -use tokio_util::sync::CancellationToken; - -/// A global state manager for Agent or Tool -/// -/// Wraps any type `S` to provide shared state management with -/// automatic dereferencing capabilities -#[derive(Debug, Default, Clone, Copy)] -pub struct State(pub S); - -impl Deref for State { - type Target = S; - - /// Provides immutable access to the inner state - fn deref(&self) -> &Self::Target { - &self.0 - } -} +use candid::{utils::ArgumentEncoder, CandidType}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{future::Future, time::Duration}; + +pub use candid::Principal; +pub use object_store::{path::Path, ObjectMeta, PutMode, PutResult}; +pub use serde_json::Value; +pub use tokio_util::sync::CancellationToken; + +use crate::BoxError; + +/// AgentContext provides the execution environment for Agents. +/// It combines core functionality with AI-specific features: +/// - BaseContext: Fundamental operations +/// - CompletionFeatures: LLM completions and function calling +/// - EmbeddingFeatures: Text embeddings +/// - VectorSearchFeatures: Semantic search +pub trait AgentContext: + BaseContext + + CompletionFeatures + + EmbeddingFeatures + + VectorSearchFeatures +{ + /// Executes a local tool with provided arguments + fn tool_call( + &self, + tool_name: &str, + args: &Value, + ) -> impl Future> + Send; + + /// Executes a remote tool on another agent + fn remote_tool_call( + &self, + endpoint: &str, + tool_name: &str, + args: &Value, + ) -> impl Future> + Send; -impl DerefMut for State { - /// Provides mutable access to the inner state - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } + /// Runs a local agent with optional attachment + fn agent_run( + &self, + agent_name: &str, + prompt: &str, + attachment: Option, + ) -> impl Future> + Send; + + /// Runs a remote agent on another endpoint + fn remote_agent_run( + &self, + endpoint: &str, + agent_name: &str, + prompt: &str, + attachment: Option, + ) -> impl Future> + Send; } /// BaseContext is the core context interface available when calling Agent or Tool. @@ -48,21 +70,18 @@ pub trait BaseContext: + HttpFeatures { /// Error type for all context operations - type Error: std::error::Error; + type Error: Into; /// Gets the username from request context. /// Note: This is not verified and should not be used as a trusted identifier. /// For example, if triggered by a bot of X platform, this might be the username /// of the user interacting with the bot. - fn user() -> String; - - /// Gets current unix timestamp in milliseconds - fn unix_ms() -> u64; + fn user(&self) -> String; /// Gets the verified caller principal if available. /// A non-None value indicates the request has been verified /// using ICP blockchain's signature verification algorithm. - fn caller() -> Option; + fn caller(&self) -> Option; /// Gets the cancellation token for the current execution context. /// Each call level has its own token scope. @@ -70,36 +89,146 @@ pub trait BaseContext: /// a child token of the agent's token. /// Cancelling the agent's token will cancel all its child calls, /// but cancelling a tool's token won't affect its parent agent. - fn cancellation_token() -> CancellationToken; + fn cancellation_token(&self) -> CancellationToken; + + /// Gets the time elapsed since the original context was created + fn time_elapsed(&self) -> Duration; + + /// Gets current unix timestamp in milliseconds + fn unix_ms() -> u64; + + /// Generates N random bytes + fn rand_bytes() -> [u8; N]; +} + +/// Represents the output of an agent execution +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct AgentOutput { + /// The output content from the agent, may be empty + pub content: String, + + /// Indicates failure reason if present, None means successful execution + /// Should be None when finish_reason is "stop" or "tool_calls" + pub failed_reason: Option, + + /// The function name to call when using Function Calling + pub function: Option, + + /// Extracted valid JSON when using Function Calling or JSON output mode + /// If no valid JSON is extracted, the raw content remains in `content` + pub extracted_json: Option, +} + +/// Represents a message in the agent's conversation history +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct Message { + /// Message role: "system", "user", or "assistant" + pub role: String, + + /// The content of the message + pub content: String, +} + +/// Defines a callable function with its metadata and schema +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct FunctionDefinition { + /// Name of the function + pub name: String, + + /// Description of what the function does + pub description: String, + + /// JSON schema defining the function's parameters + pub parameters: serde_json::Value, +} + +/// Provides LLM completion capabilities for agents +pub trait CompletionFeatures: Sized { + /// Generates a completion based on the given prompt and context + /// + /// # Arguments + /// * `prompt` - The input prompt for the completion + /// * `json_output` - Whether to force JSON output format + /// * `chat_history` - Conversation history as context + /// * `tools` - Available functions the model can call + fn completion( + &self, + prompt: &str, + json_output: bool, + chat_history: &[Message], + tools: &[FunctionDefinition], + ) -> impl Future> + Send; +} + +/// Represents a text embedding with its original text and vector representation +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct Embedding { + /// The original text that was embedded + pub text: String, + + /// The embedding vector (typically high-dimensional float array) + pub vec: Vec, +} + +/// Provides text embedding capabilities for agents +pub trait EmbeddingFeatures: Sized { + /// Generates embeddings for multiple texts in a batch + /// Returns a vector of Embedding structs in the same order as input texts + fn embed( + &self, + texts: impl IntoIterator + Send, + ) -> impl Future, Err>> + Send; + + /// Generates a single embedding for a query text + /// Optimized for single text embedding generation + fn embed_query(&self, text: &str) -> impl Future> + Send; +} + +/// Provides vector search capabilities for semantic similarity search +pub trait VectorSearchFeatures: Sized { + /// Performs a semantic search to find top n most similar documents + /// Returns a list of tuples containing (document_id, deserialized_document) + fn top_n( + &self, + query: &str, + n: usize, + ) -> impl Future, Err>> + Send + where + T: DeserializeOwned; + + /// Performs a semantic search but returns only document IDs + /// More efficient when only document identifiers are needed + fn top_n_ids( + &self, + query: &str, + n: usize, + ) -> impl std::future::Future, Err>> + Send; } /// KeysFeatures is one of the context feature sets available when calling Agent or Tool. -/// +/// /// The Agent engine running in TEE has a permanent fixed 48-bit root key, /// from which AES, Ed25519, Secp256k1 keys are derived. /// The Agent/Tool name is included in key derivation, ensuring isolation /// even with the same derivation path. pub trait KeysFeatures: Sized { - /// Generates N random bytes - fn rand_bytes() -> [u8; N]; - /// Derives a 256-bit AES-GCM key from the given derivation path fn a256gcm_key( &self, - derivation_path: Vec>, + derivation_path: &[&[u8]], ) -> impl Future> + Send; /// Signs a message using Ed25519 signature scheme from the given derivation path fn ed25519_sign_message( &self, - derivation_path: Vec>, + derivation_path: &[&[u8]], message: &[u8], ) -> impl Future> + Send; /// Verifies an Ed25519 signature from the given derivation path fn ed25519_verify( &self, - derivation_path: Vec>, + derivation_path: &[&[u8]], message: &[u8], signature: &[u8], ) -> impl Future> + Send; @@ -107,20 +236,20 @@ pub trait KeysFeatures: Sized { /// Gets the public key for Ed25519 from the given derivation path fn ed25519_public_key( &self, - derivation_path: Vec>, + derivation_path: &[&[u8]], ) -> impl Future> + Send; /// Signs a message using Secp256k1 BIP340 Schnorr signature from the given derivation path fn secp256k1_sign_message_bip340( &self, - derivation_path: Vec>, - msg: &[u8], + derivation_path: &[&[u8]], + message: &[u8], ) -> impl Future> + Send; /// Verifies a Secp256k1 BIP340 Schnorr signature from the given derivation path fn secp256k1_verify_bip340( &self, - derivation_path: Vec>, + derivation_path: &[&[u8]], message: &[u8], signature: &[u8], ) -> impl Future> + Send; @@ -128,14 +257,14 @@ pub trait KeysFeatures: Sized { /// Signs a message using Secp256k1 ECDSA signature from the given derivation path fn secp256k1_sign_message_ecdsa( &self, - derivation_path: Vec>, - msg: &[u8], + derivation_path: &[&[u8]], + message: &[u8], ) -> impl Future> + Send; /// Verifies a Secp256k1 ECDSA signature from the given derivation path fn secp256k1_verify_ecdsa( &self, - derivation_path: Vec>, + derivation_path: &[&[u8]], message: &[u8], signature: &[u8], ) -> impl Future> + Send; @@ -143,31 +272,34 @@ pub trait KeysFeatures: Sized { /// Gets the compressed SEC1-encoded public key for Secp256k1 from the given derivation path fn secp256k1_public_key( &self, - derivation_path: Vec>, + derivation_path: &[&[u8]], ) -> impl Future> + Send; } /// StoreFeatures is one of the context feature sets available when calling Agent or Tool. -/// +/// /// Provides persistent storage capabilities for Agents and Tools to store and manage data. /// All operations are asynchronous and return Result types with custom error handling. pub trait StoreFeatures: Sized { /// Retrieves data from storage at the specified path - fn store_get(&self, path: &Path) -> impl Future> + Send; + fn store_get( + &self, + path: &Path, + ) -> impl Future> + Send; /// Lists objects in storage with optional prefix and offset filters - /// + /// /// # Arguments /// * `prefix` - Optional path prefix to filter results /// * `offset` - Optional path to start listing from (exclude) fn store_list( &self, prefix: Option<&Path>, - offset: Option<&Path>, + offset: &Path, ) -> impl Future, Err>> + Send; /// Stores data at the specified path with a given write mode - /// + /// /// # Arguments /// * `path` - Target storage path /// * `mode` - Write mode (Create, Overwrite, etc.) @@ -177,10 +309,10 @@ pub trait StoreFeatures: Sized { path: &Path, mode: PutMode, val: bytes::Bytes, - ) -> impl Future> + Send; + ) -> impl Future> + Send; /// Renames a storage object if the target path doesn't exist - /// + /// /// # Arguments /// * `from` - Source path /// * `to` - Destination path @@ -191,7 +323,7 @@ pub trait StoreFeatures: Sized { ) -> impl Future> + Send; /// Deletes data at the specified path - /// + /// /// # Arguments /// * `path` - Path of the object to delete fn store_delete(&self, path: &Path) -> impl Future> + Send; @@ -207,7 +339,7 @@ pub enum CacheExpiry { } /// CacheFeatures is one of the context feature sets available when calling Agent or Tool. -/// +/// /// Provides isolated in-memory cache storage with TTL/TTI expiration. /// Cache data is ephemeral and will be lost on engine restart. pub trait CacheFeatures: Sized { @@ -215,12 +347,12 @@ pub trait CacheFeatures: Sized { fn cache_contains(&self, key: &str) -> bool; /// Gets a cached value by key, returns error if not found or deserialization fails - fn cache_get(&self, key: &str) -> Result + fn cache_get(&self, key: &str) -> impl Future> + Send where T: DeserializeOwned; /// Gets a cached value or initializes it if missing - /// + /// /// If key doesn't exist, calls init function to create value and cache it fn cache_get_with( &self, @@ -228,26 +360,30 @@ pub trait CacheFeatures: Sized { init: F, ) -> impl Future> + Send where - T: DeserializeOwned + Serialize, - F: Future), Err>>; + T: Sized + DeserializeOwned + Serialize + Send, + F: Future), Err>> + Send + 'static; /// Sets a value in cache with optional expiration policy - fn cache_set(&self, key: &str, val: (V, Option)) + fn cache_set( + &self, + key: &str, + val: (T, Option), + ) -> impl Future + Send where - V: Sized + Serialize; + T: Sized + Serialize + Send; /// Deletes a cached value by key, returns true if key existed - fn cache_delete(&self, key: &str) -> bool; + fn cache_delete(&self, key: &str) -> impl Future + Send; } /// CanisterFeatures is one of the context feature sets available when calling Agent or Tool. -/// +/// /// Allows Agents/Tools to interact with any canister contract on the ICP blockchain. /// The Agent engine will sign canister requests, and they share the same identity ID. /// A single TEE instance runs only one Agent engine and has only one ICP identity. pub trait CanisterFeatures: Sized { /// Performs a query call to a canister (read-only, no state changes) - /// + /// /// # Arguments /// * `canister` - Target canister principal /// * `method` - Method name to call @@ -263,7 +399,7 @@ pub trait CanisterFeatures: Sized { ) -> impl Future> + Send; /// Performs an update call to a canister (may modify state) - /// + /// /// # Arguments /// * `canister` - Target canister principal /// * `method` - Method name to call @@ -280,13 +416,13 @@ pub trait CanisterFeatures: Sized { } /// HttpFeatures provides HTTP request capabilities for Agents and Tools -/// +/// /// All HTTP requests are managed and scheduled by the Agent engine. /// Since Agents may run in WASM containers, implementations should not /// implement HTTP requests directly. pub trait HttpFeatures: Sized { /// Makes an HTTPs request - /// + /// /// # Arguments /// * `url` - Target URL, should start with `https://` /// * `method` - HTTP method (GET, POST, etc.) @@ -295,13 +431,13 @@ pub trait HttpFeatures: Sized { fn https_call( &self, url: &str, - method: &http::Method, + method: http::Method, headers: Option, body: Option>, // default is empty - ) -> impl Future> + Send; + ) -> impl Future> + Send; /// Makes a signed HTTPs request with message authentication - /// + /// /// # Arguments /// * `url` - Target URL /// * `method` - HTTP method (GET, POST, etc.) @@ -311,9 +447,24 @@ pub trait HttpFeatures: Sized { fn https_signed_call( &self, url: &str, - method: &http::Method, + method: http::Method, message_digest: &[u8; 32], headers: Option, body: Option>, // default is empty - ) -> impl Future> + Send; + ) -> impl Future> + Send; + + /// Makes a signed CBOR-encoded RPC call + /// + /// # Arguments + /// * `endpoint` - URL endpoint to send the request to + /// * `method` - RPC method name to call + /// * `params` - Parameters to serialize as CBOR and send with the request + fn https_signed_rpc( + &self, + endpoint: &str, + method: &str, + params: impl Serialize + Send, + ) -> impl Future> + Send + where + T: DeserializeOwned; } diff --git a/anda_core/src/http.rs b/anda_core/src/http.rs new file mode 100644 index 0000000..9bb7a32 --- /dev/null +++ b/anda_core/src/http.rs @@ -0,0 +1,214 @@ +use candid::{decode_args, encode_args, utils::ArgumentEncoder, CandidType, Principal}; +use ciborium::from_reader; +use http::header; +use ic_cose_types::to_cbor_bytes; +use reqwest::Client; +use serde::{de::DeserializeOwned, Serialize}; +use serde_bytes::ByteBuf; +use std::fmt::Display; + +use crate::BoxError; + +pub static CONTENT_TYPE_CBOR: &str = "application/cbor"; +pub static CONTENT_TYPE_JSON: &str = "application/json"; +pub static CONTENT_TYPE_TEXT: &str = "text/plain"; + +/// Represents an RPC request with method name and CBOR-encoded parameters +#[derive(Clone, Debug, Serialize)] +pub struct RPCRequest<'a> { + pub method: &'a str, + /// CBOR-encoded parameters for the RPC call. + /// Parameters should be provided as a tuple, where each element represents a single parameter. + /// Examples: + /// - `()`: No parameters + /// - `(1,)`: Single parameter + /// - `(1, "hello", 3.14)`: Three parameters + pub params: &'a ByteBuf, +} + +/// Represents a request to an ICP canister with canister ID, method name, and Candid-encoded parameters +#[derive(Clone, Debug, Serialize)] +pub struct CanisterRequest<'a> { + /// The target canister's principal ID + pub canister: &'a Principal, + /// The method name to call on the canister + pub method: &'a str, + /// Candid-encoded parameters for the canister call. + /// Parameters should be provided as a tuple, where each element represents a single parameter. + /// Examples: + /// - `()`: No parameters + /// - `(1,)`: Single parameter + /// - `(1, "hello", 3.14)`: Three parameters + pub params: &'a ByteBuf, +} + +/// Represents an RPC response that can be either: +/// - Ok(ByteBuf): CBOR or Candid encoded successful response +/// - Err(String): Error message as a string +pub type RPCResponse = Result; + +/// Possible errors when working with http_rpc +#[derive(Debug, thiserror::Error)] +pub enum HttpRPCError { + #[error("http_rpc({endpoint}, {path}): send error: {error}")] + RequestError { + endpoint: String, + path: String, + error: BoxError, + }, + + #[error("http_rpc({endpoint}, {path}): response status {status}, error: {error}")] + ResponseError { + endpoint: String, + path: String, + status: u16, + error: String, + }, + + #[error("http_rpc({endpoint}, {path}): parse result error: {error}")] + ResultError { + endpoint: String, + path: String, + error: BoxError, + }, +} + +/// Makes an HTTP RPC call with CBOR-encoded parameters and returns the decoded response +/// +/// # Arguments +/// * `client` - HTTP client to use for the request +/// * `endpoint` - URL endpoint to send the request to +/// * `method` - RPC method name to call +/// * `params` - Parameters to serialize as CBOR and send with the request +/// +/// # Returns +/// Result with either the deserialized response or an HttpRPCError +pub async fn http_rpc( + client: &Client, + endpoint: &str, + method: &str, + params: &impl Serialize, +) -> Result +where + T: DeserializeOwned, +{ + let params = to_cbor_bytes(params); + let req = RPCRequest { + method, + params: ¶ms.into(), + }; + + let res = cbor_rpc(client, endpoint, method, None, to_cbor_bytes(&req)).await?; + from_reader(&res[..]).map_err(|e| HttpRPCError::ResultError { + endpoint: endpoint.to_string(), + path: method.to_string(), + error: e.into(), + }) +} + +/// Makes a canister-specific RPC call with Candid-encoded parameters +/// +/// # Arguments +/// * `client` - HTTP client to use for the request +/// * `endpoint` - URL endpoint to send the request to +/// * `canister` - Target canister's principal ID +/// * `method` - Method name to call on the canister +/// * `args` - Arguments to encode using Candid +/// +/// # Returns +/// Result with either the deserialized response or an HttpRPCError +pub async fn canister_rpc( + client: &Client, + endpoint: &str, + canister: &Principal, + method: &str, + args: In, +) -> Result +where + In: ArgumentEncoder, + Out: CandidType + for<'a> candid::Deserialize<'a>, +{ + let params = encode_args(args).map_err(|e| HttpRPCError::RequestError { + endpoint: endpoint.to_string(), + path: canister.to_string(), + error: e.into(), + })?; + let res = cbor_rpc( + client, + endpoint, + canister, + None, + to_cbor_bytes(&CanisterRequest { + canister, + method, + params: &ByteBuf::from(params), + }), + ) + .await?; + let res: (Out,) = decode_args(&res).map_err(|e| HttpRPCError::ResultError { + endpoint: endpoint.to_string(), + path: canister.to_string(), + error: e.into(), + })?; + Ok(res.0) +} + +/// Internal function to make a CBOR-encoded RPC call +/// +/// # Arguments +/// * `client` - HTTP client to use for the request +/// * `endpoint` - URL endpoint to send the request to +/// * `path` - Path or identifier for the request +/// * `headers` - Optional headers to include in the request +/// * `body` - CBOR-encoded request body +/// +/// # Returns +/// Result with either the raw ByteBuf response or an HttpRPCError +pub async fn cbor_rpc( + client: &Client, + endpoint: &str, + path: impl Display, + headers: Option, + body: Vec, +) -> Result { + let mut headers = headers.unwrap_or_default(); + let cb: http::HeaderValue = CONTENT_TYPE_CBOR.parse().unwrap(); + headers.insert(header::CONTENT_TYPE, cb.clone()); + headers.insert(header::ACCEPT, cb); + let res = client + .post(endpoint) + .headers(headers) + .body(body) + .send() + .await + .map_err(|e| HttpRPCError::RequestError { + endpoint: endpoint.to_string(), + path: path.to_string(), + error: e.into(), + })?; + let status = res.status().as_u16(); + if status != 200 { + return Err(HttpRPCError::ResponseError { + endpoint: endpoint.to_string(), + path: path.to_string(), + status, + error: res.text().await.unwrap_or_default(), + }); + } + + let data = res.bytes().await.map_err(|e| HttpRPCError::ResultError { + endpoint: endpoint.to_string(), + path: path.to_string(), + error: e.into(), + })?; + let res: RPCResponse = from_reader(&data[..]).map_err(|e| HttpRPCError::ResultError { + endpoint: endpoint.to_string(), + path: path.to_string(), + error: e.into(), + })?; + res.map_err(|e| HttpRPCError::ResultError { + endpoint: endpoint.to_string(), + path: path.to_string(), + error: e.into(), + }) +} diff --git a/anda_core/src/lib.rs b/anda_core/src/lib.rs index b095913..4acf74a 100644 --- a/anda_core/src/lib.rs +++ b/anda_core/src/lib.rs @@ -1,17 +1,38 @@ +use std::ops::{Deref, DerefMut}; pub mod agent; pub mod context; +pub mod http; pub mod tool; +pub use agent::*; +pub use context::*; +pub use http::*; +pub use tool::*; + /// A type alias for a boxed error that is thread-safe and sendable across threads. /// This is commonly used as a return type for functions that can return various error types. pub type BoxError = Box; -/// Represents an HTTP response with status code, headers, and optional body. -/// This struct is used to encapsulate HTTP response data in a structured way. -#[derive(Debug, Clone, Default)] -pub struct HttpResponse { - pub status: http::StatusCode, - pub headers: http::header::HeaderMap, - pub body: Option, +/// A global state manager for Agent or Tool +/// +/// Wraps any type `S` to provide shared state management with +/// automatic dereferencing capabilities +#[derive(Debug, Default, Clone, Copy)] +pub struct State(pub S); + +impl Deref for State { + type Target = S; + + /// Provides immutable access to the inner state + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for State { + /// Provides mutable access to the inner state + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } diff --git a/anda_core/src/tool.rs b/anda_core/src/tool.rs index 0652622..cdb025e 100644 --- a/anda_core/src/tool.rs +++ b/anda_core/src/tool.rs @@ -1,23 +1,25 @@ -use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{collections::BTreeMap, future::Future, marker::PhantomData, pin::Pin}; -use crate::{context::BaseContext, BoxError}; - -/// Defines the metadata and schema for a tool -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ToolDefinition { - pub name: String, - pub description: String, - pub parameters: serde_json::Value, -} +use crate::{ + context::{BaseContext, FunctionDefinition}, + BoxError, +}; /// Core trait for implementing tools that can be used by the AI Agent system +/// +/// # Type Parameters +/// - `C`: The context type that implements `BaseContext`, must be thread-safe and have a static lifetime pub trait Tool: Send + Sync where C: BaseContext + Send + Sync + 'static, { - /// The name of the tool. This name should be unique in the engine. + /// The unique name of the tool. This name should be unique within the engine. + /// + /// # Rules + /// - Must not be empty + /// - Length must be ≤ 64 characters + /// - Can only contain: lowercase letters (a-z), digits (0-9), and underscores (_) const NAME: &'static str; /// Returns the tool's name @@ -25,13 +27,21 @@ where Self::NAME.to_string() } - /// Provides the tool's definition including its parameters schema. The user prompt can be used to - /// tailor the definition to the specific use case. - fn definition(&self, prompt: Option<&str>) -> ToolDefinition; + /// Provides the tool's definition including its parameters schema. + /// + /// # Returns + /// - `FunctionDefinition`: The schema definition of the tool's parameters and metadata + fn definition(&self) -> FunctionDefinition; /// Executes the tool with given context and arguments - /// Both the arguments and return value are a JSON since these values are meant to - /// be the input and output of LLM models (respectively) + /// + /// # Arguments + /// - `ctx`: The execution context implementing `BaseContext` + /// - `args`: JSON value containing the input arguments for the tool + /// + /// # Returns + /// - A future resolving to a JSON value containing the tool's output + /// - Returns `BoxError` if execution fails fn call( &self, ctx: &C, @@ -40,14 +50,20 @@ where } /// Dynamic dispatch version of the Tool trait +/// +/// This trait allows for runtime polymorphism of tools, enabling different tool implementations +/// to be stored and called through a common interface. pub trait ToolDyn: Send + Sync where C: BaseContext + Send + Sync + 'static, { + /// Returns the tool's name as a String fn name(&self) -> String; - fn definition(&self, prompt: Option<&str>) -> ToolDefinition; + /// Provides the tool's definition including its parameters schema + fn definition(&self) -> FunctionDefinition; + /// Executes the tool with given context and arguments using dynamic dispatch fn call<'a>( &'a self, ctx: &'a C, @@ -70,8 +86,8 @@ where self.0.name() } - fn definition(&self, prompt: Option<&str>) -> ToolDefinition { - self.0.definition(prompt) + fn definition(&self) -> FunctionDefinition { + self.0.definition() } fn call<'a>( @@ -83,57 +99,96 @@ where } } -/// Possible errors when working with tools -#[derive(Debug, thiserror::Error)] -pub enum ToolSetError { - /// Error returned by the tool - #[error("tool call error: {0}")] - ToolCallError(#[from] BoxError), - - #[error("tool not found: {0}")] - ToolNotFoundError(String), -} - /// Collection of tools that can be used by the AI Agent +/// +/// # Type Parameters +/// - `C`: The context type that implements `BaseContext`, must have a static lifetime #[derive(Default)] pub struct ToolSet { - pub(crate) tools: BTreeMap>>, + pub set: BTreeMap>>, } impl ToolSet where C: BaseContext + Send + Sync + 'static, { + /// Creates a new empty ToolSet pub fn new() -> Self { Self { - tools: BTreeMap::new(), + set: BTreeMap::new(), } } - pub fn contains(&self, toolname: &str) -> bool { - self.tools.contains_key(toolname) + /// Checks if a tool with the given name exists in the set + pub fn contains(&self, name: &str) -> bool { + self.set.contains_key(name) + } + + /// Gets the definition of a specific tool by name + /// + /// # Returns + /// - `Some(FunctionDefinition)` if the tool exists + /// - `None` if the tool is not found + pub fn definition(&self, name: &str) -> Option { + self.set.get(name).map(|tool| tool.definition()) + } + + /// Gets definitions for multiple tools, optionally filtered by names + /// + /// # Arguments + /// - `names`: Optional slice of tool names to filter by. If None or empty, returns all definitions + /// + /// # Returns + /// - Vector of `FunctionDefinition` for the requested tools + pub fn definitions(&self, names: Option<&[&str]>) -> Vec { + let names = names.unwrap_or_default(); + self.set + .iter() + .filter_map(|(name, tool)| { + if names.is_empty() || names.contains(&name.as_str()) { + Some(tool.definition()) + } else { + None + } + }) + .collect() } + /// Adds a new tool to the set + /// + /// # Arguments + /// - `tool`: The tool to add, must implement the `Tool` trait pub fn add(&mut self, tool: T) where T: Tool + 'static, { let tool_dyn = ToolWrapper(tool, PhantomData); - self.tools.insert(T::NAME.to_string(), Box::new(tool_dyn)); + self.set.insert(T::NAME.to_string(), Box::new(tool_dyn)); } + /// Calls a tool by name with the given context and arguments + /// + /// # Arguments + /// - `name`: The name of the tool to call + /// - `ctx`: The execution context + /// - `args`: JSON value containing the input arguments + /// + /// # Returns + /// - A future resolving to the tool's output as a JSON value + /// - Returns an error if the tool is not found pub fn call<'a>( &'a self, - toolname: &str, + name: &str, ctx: &'a C, args: &'a Value, ) -> Pin> + Send + 'a>> { - if let Some(tool) = self.tools.get(toolname) { + if let Some(tool) = self.set.get(name) { tool.call(ctx, args) } else { - Box::pin(futures::future::ready(Err( - Box::new(ToolSetError::ToolNotFoundError(toolname.to_string())) as BoxError, - ))) + Box::pin(futures::future::ready(Err(format!( + "tool {name} not found" + ) + .into()))) } } } diff --git a/anda_engine/Cargo.toml b/anda_engine/Cargo.toml new file mode 100644 index 0000000..b88961e --- /dev/null +++ b/anda_engine/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "anda_engine" +description = "Agents engine for Anda -- a framework for AI agent development." +repository = "https://github.com/ldclabs/anda/tree/main/anda_engine" +publish = true +version = "0.1.0" +edition.workspace = true +keywords.workspace = true +categories.workspace = true +license.workspace = true + +[dependencies] +anda_core = { path = "../anda_core", version = "0.2" } +candid = { workspace = true } +bytes = { workspace = true } +ciborium = { workspace = true } +futures = { workspace = true } +futures-util = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_bytes = { workspace = true } +http = { workspace = true } +thiserror = { workspace = true } +object_store = { workspace = true } +ic_cose = { workspace = true } +ic_cose_types = { workspace = true } +ic_object_store = { workspace = true } +ic_tee_agent = { workspace = true } +tokio-util = { workspace = true } +structured-logger = { workspace = true } +reqwest = { workspace = true } +moka = { workspace = true } + +[dev-dependencies] +const-hex = { workspace = true } diff --git a/anda_engine/README.md b/anda_engine/README.md new file mode 100644 index 0000000..93b92a7 --- /dev/null +++ b/anda_engine/README.md @@ -0,0 +1,22 @@ +# `anda_engine` + +[![Crates.io](https://img.shields.io/crates/v/anda_engine)](https://crates.io/crates/anda_engine) +[![Documentation](https://docs.rs/anda_engine/badge.svg)](https://docs.rs/anda_engine) + +Agents engine for Anda. + +More information about this crate can be found in the [crate documentation][docs]. + +## License +Copyright © 2025 [LDC Labs](https://github.com/ldclabs). + +`ldclabs/anda` is licensed under the MIT License. See the [MIT license][license] for the full license text. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in `anda` by you, shall be licensed as MIT, without any +additional terms or conditions. + +[docs]: https://docs.rs/anda_engine +[license]: ./../LICENSE-MIT \ No newline at end of file diff --git a/anda_engine/src/context/agent.rs b/anda_engine/src/context/agent.rs new file mode 100644 index 0000000..82fb186 --- /dev/null +++ b/anda_engine/src/context/agent.rs @@ -0,0 +1,478 @@ +use anda_core::{ + AgentContext, AgentOutput, AgentSet, BaseContext, BoxError, CacheExpiry, CacheFeatures, + CancellationToken, CanisterFeatures, CompletionFeatures, Embedding, EmbeddingFeatures, + FunctionDefinition, HttpFeatures, KeysFeatures, Message, ObjectMeta, Path, PutMode, PutResult, + StoreFeatures, ToolSet, Value, VectorSearchFeatures, +}; +use candid::{utils::ArgumentEncoder, CandidType, Principal}; +use serde::{de::DeserializeOwned, Serialize}; +use std::{future::Future, sync::Arc, time::Duration}; + +use super::base::BaseCtx; + +pub struct AgentCtx { + pub(crate) base: BaseCtx, + pub(crate) tools: Arc>, + pub(crate) agents: Arc>, +} + +impl AgentCtx { + pub fn new( + base: BaseCtx, + tools: Arc>, + agents: Arc>, + ) -> Self { + Self { + base, + tools, + agents, + } + } + + pub fn child(&self, agent_name: &str) -> Result { + Ok(Self { + base: self.base.child(format!("A:{}", agent_name))?, + tools: self.tools.clone(), + agents: self.agents.clone(), + }) + } + + pub fn child_base(&self, tool_name: &str) -> Result { + self.base.child(format!("T:{}", tool_name)) + } + + pub fn child_with( + &self, + agent_name: &str, + user: String, + caller: Option, + ) -> Result { + Ok(Self { + base: self + .base + .child_with(format!("A:{}", agent_name), user, caller)?, + tools: self.tools.clone(), + agents: self.agents.clone(), + }) + } + + pub fn child_base_with( + &self, + tool_name: &str, + user: String, + caller: Option, + ) -> Result { + self.base + .child_with(format!("T:{}", tool_name), user, caller) + } +} + +impl AgentContext for AgentCtx { + async fn tool_call(&self, name: &str, args: &Value) -> Result { + if !self.tools.contains(name) { + return Err(format!("tool {} not found", name).into()); + } + + let ctx = self.child_base(name)?; + self.tools.call(name, &ctx, args).await + } + + async fn remote_tool_call( + &self, + endpoint: &str, + tool_name: &str, + args: &Value, + ) -> Result { + self.https_signed_rpc(endpoint, "tool_call", &(tool_name, args)) + .await + } + + async fn agent_run( + &self, + name: &str, + prompt: &str, + attachment: Option, + ) -> Result { + if !self.agents.contains(name) { + return Err(format!("agent {} not found", name).into()); + } + + let ctx = self.child(name)?; + self.agents.run(name, &ctx, prompt, attachment).await + } + + async fn remote_agent_run( + &self, + endpoint: &str, + agent_name: &str, + prompt: &str, + attachment: Option, + ) -> Result { + self.https_signed_rpc(endpoint, "agent_run", &(agent_name, prompt, attachment)) + .await + } +} + +impl CompletionFeatures for AgentCtx { + async fn completion( + &self, + prompt: &str, + json_output: bool, + chat_history: &[Message], + tools: &[FunctionDefinition], + ) -> Result { + Err("Not implemented".into()) + } +} + +impl EmbeddingFeatures for AgentCtx { + async fn embed( + &self, + texts: impl IntoIterator + Send, + ) -> Result, BoxError> { + Err("Not implemented".into()) + } + + async fn embed_query(&self, text: &str) -> Result { + Err("Not implemented".into()) + } +} + +impl VectorSearchFeatures for AgentCtx { + /// Get the top n documents based on the distance to the given query. + /// The result is a list of tuples of the form (score, id, document) + async fn top_n(&self, query: &str, n: usize) -> Result, BoxError> { + Err("Not implemented".into()) + } + + /// Same as `top_n` but returns the document ids only. + async fn top_n_ids(&self, query: &str, n: usize) -> Result, BoxError> { + Err("Not implemented".into()) + } +} + +impl BaseContext for AgentCtx { + type Error = BoxError; + + fn user(&self) -> String { + self.base.user() + } + + fn caller(&self) -> Option { + self.base.caller() + } + + fn cancellation_token(&self) -> CancellationToken { + self.base.cancellation_token() + } + + fn time_elapsed(&self) -> Duration { + self.base.time_elapsed() + } + + fn unix_ms() -> u64 { + BaseCtx::unix_ms() + } + + fn rand_bytes() -> [u8; N] { + BaseCtx::rand_bytes() + } +} + +impl KeysFeatures for AgentCtx { + /// Derives a 256-bit AES-GCM key from the given derivation path + async fn a256gcm_key(&self, derivation_path: &[&[u8]]) -> Result<[u8; 32], BoxError> { + self.base.a256gcm_key(derivation_path).await + } + + /// Signs a message using Ed25519 signature scheme from the given derivation path + async fn ed25519_sign_message( + &self, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + self.base + .ed25519_sign_message(derivation_path, message) + .await + } + + /// Verifies an Ed25519 signature from the given derivation path + async fn ed25519_verify( + &self, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + self.base + .ed25519_verify(derivation_path, message, signature) + .await + } + + /// Gets the public key for Ed25519 from the given derivation path + async fn ed25519_public_key(&self, derivation_path: &[&[u8]]) -> Result<[u8; 32], BoxError> { + self.base.ed25519_public_key(derivation_path).await + } + + /// Signs a message using Secp256k1 BIP340 Schnorr signature from the given derivation path + async fn secp256k1_sign_message_bip340( + &self, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + self.base + .secp256k1_sign_message_bip340(derivation_path, message) + .await + } + + /// Verifies a Secp256k1 BIP340 Schnorr signature from the given derivation path + async fn secp256k1_verify_bip340( + &self, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + self.base + .secp256k1_verify_bip340(derivation_path, message, signature) + .await + } + + /// Signs a message using Secp256k1 ECDSA signature from the given derivation path + async fn secp256k1_sign_message_ecdsa( + &self, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + self.base + .secp256k1_sign_message_ecdsa(derivation_path, message) + .await + } + + /// Verifies a Secp256k1 ECDSA signature from the given derivation path + async fn secp256k1_verify_ecdsa( + &self, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + self.base + .secp256k1_verify_ecdsa(derivation_path, message, signature) + .await + } + + /// Gets the compressed SEC1-encoded public key for Secp256k1 from the given derivation path + async fn secp256k1_public_key(&self, derivation_path: &[&[u8]]) -> Result<[u8; 33], BoxError> { + self.base.secp256k1_public_key(derivation_path).await + } +} + +impl StoreFeatures for AgentCtx { + /// Retrieves data from storage at the specified path + async fn store_get(&self, path: &Path) -> Result<(bytes::Bytes, ObjectMeta), BoxError> { + self.base.store_get(path).await + } + + /// Lists objects in storage with optional prefix and offset filters + /// + /// # Arguments + /// * `prefix` - Optional path prefix to filter results + /// * `offset` - Optional path to start listing from (exclude) + async fn store_list( + &self, + prefix: Option<&Path>, + offset: &Path, + ) -> Result, BoxError> { + self.base.store_list(prefix, offset).await + } + + /// Stores data at the specified path with a given write mode + /// + /// # Arguments + /// * `path` - Target storage path + /// * `mode` - Write mode (Create, Overwrite, etc.) + /// * `val` - Data to store as bytes + async fn store_put( + &self, + path: &Path, + mode: PutMode, + val: bytes::Bytes, + ) -> Result { + self.base.store_put(path, mode, val).await + } + + /// Renames a storage object if the target path doesn't exist + /// + /// # Arguments + /// * `from` - Source path + /// * `to` - Destination path + async fn store_rename_if_not_exists(&self, from: &Path, to: &Path) -> Result<(), BoxError> { + self.base.store_rename_if_not_exists(from, to).await + } + + /// Deletes data at the specified path + /// + /// # Arguments + /// * `path` - Path of the object to delete + async fn store_delete(&self, path: &Path) -> Result<(), BoxError> { + self.base.store_delete(path).await + } +} + +impl CacheFeatures for AgentCtx { + /// Checks if a key exists in the cache + fn cache_contains(&self, key: &str) -> bool { + self.base.cache_contains(key) + } + + /// Gets a cached value by key, returns error if not found or deserialization fails + async fn cache_get(&self, key: &str) -> Result + where + T: DeserializeOwned, + { + self.base.cache_get(key).await + } + + /// Gets a cached value or initializes it if missing + /// + /// If key doesn't exist, calls init function to create value and cache it + async fn cache_get_with(&self, key: &str, init: F) -> Result + where + T: Sized + DeserializeOwned + Serialize + Send, + F: Future), BoxError>> + Send + 'static, + { + // futures_util::pin_mut!(init); + self.base.cache_get_with(key, init).await + } + + /// Sets a value in cache with optional expiration policy + async fn cache_set(&self, key: &str, val: (T, Option)) + where + T: Sized + Serialize + Send, + { + self.base.cache_set(key, val).await + } + + /// Deletes a cached value by key, returns true if key existed + async fn cache_delete(&self, key: &str) -> bool { + self.base.cache_delete(key).await + } +} + +impl CanisterFeatures for AgentCtx { + /// Performs a query call to a canister (read-only, no state changes) + /// + /// # Arguments + /// * `canister` - Target canister principal + /// * `method` - Method name to call + /// * `args` - Input arguments encoded in Candid format + async fn canister_query< + In: ArgumentEncoder + Send, + Out: CandidType + for<'a> candid::Deserialize<'a>, + >( + &self, + canister: &Principal, + method: &str, + args: In, + ) -> Result { + self.base.canister_query(canister, method, args).await + } + + /// Performs an update call to a canister (may modify state) + /// + /// # Arguments + /// * `canister` - Target canister principal + /// * `method` - Method name to call + /// * `args` - Input arguments encoded in Candid format + async fn canister_update< + In: ArgumentEncoder + Send, + Out: CandidType + for<'a> candid::Deserialize<'a>, + >( + &self, + canister: &Principal, + method: &str, + args: In, + ) -> Result { + self.base.canister_update(canister, method, args).await + } +} + +impl HttpFeatures for AgentCtx { + /// Makes an HTTPs request + /// + /// # Arguments + /// * `url` - Target URL, should start with `https://` + /// * `method` - HTTP method (GET, POST, etc.) + /// * `headers` - Optional HTTP headers + /// * `body` - Optional request body (default empty) + async fn https_call( + &self, + url: &str, + method: http::Method, + headers: Option, + body: Option>, // default is empty + ) -> Result { + self.base.https_call(url, method, headers, body).await + } + + /// Makes a signed HTTPs request with message authentication + /// + /// # Arguments + /// * `url` - Target URL + /// * `method` - HTTP method (GET, POST, etc.) + /// * `message_digest` - 32-byte message digest for signing + /// * `headers` - Optional HTTP headers + /// * `body` - Optional request body (default empty) + async fn https_signed_call( + &self, + url: &str, + method: http::Method, + message_digest: &[u8; 32], + headers: Option, + body: Option>, // default is empty + ) -> Result { + self.base + .https_signed_call(url, method, message_digest, headers, body) + .await + } + + /// Makes a signed CBOR-encoded RPC call + /// + /// # Arguments + /// * `endpoint` - URL endpoint to send the request to + /// * `method` - RPC method name to call + /// * `params` - Parameters to serialize as CBOR and send with the request + async fn https_signed_rpc( + &self, + endpoint: &str, + method: &str, + params: impl Serialize + Send, + ) -> Result + where + T: DeserializeOwned, + { + self.base.https_signed_rpc(endpoint, method, params).await + } +} + +#[cfg(test)] +mod tests { + use ciborium::from_reader; + use ic_cose_types::to_cbor_bytes; + use serde_json::json; + + #[test] + fn json_in_cbor_works() { + let json = json!({ + "level": "info", + "message": "Hello, world!", + "timestamp": "2021-09-01T12:00:00Z", + "data": { + "key": "value", + "number": 42, + "flag": true + } + }); + let data = to_cbor_bytes(&json); + let val: serde_json::Value = from_reader(&data[..]).unwrap(); + assert_eq!(json, val); + } +} diff --git a/anda_engine/src/context/base.rs b/anda_engine/src/context/base.rs new file mode 100644 index 0000000..6bcc093 --- /dev/null +++ b/anda_engine/src/context/base.rs @@ -0,0 +1,506 @@ +use anda_core::{ + canister_rpc, cbor_rpc, http_rpc, BaseContext, BoxError, CacheExpiry, CacheFeatures, + CancellationToken, CanisterFeatures, HttpFeatures, HttpRPCError, KeysFeatures, ObjectMeta, + Path, PutMode, PutResult, RPCRequest, StoreFeatures, +}; +use candid::{utils::ArgumentEncoder, CandidType, Principal}; +use ciborium::from_reader; +use futures::TryStreamExt; +use ic_cose::rand_bytes; +use ic_cose_types::{cose::sha3_256, to_cbor_bytes}; +use object_store::{ObjectStore, PutOptions}; +use reqwest::Client; +use serde::{de::DeserializeOwned, Serialize}; +use std::{ + collections::HashMap, + future::Future, + sync::Arc, + time::{Duration, Instant}, +}; +use structured_logger::unix_ms; + +const CACHE_MAX_CAPACITY: u64 = 1000000; +const CONTEXT_MAX_DEPTH: u8 = 42; + +static TEE_LOCAL_SERVER: &str = "http://127.0.0.1:8080"; + +use super::{cache::CacheService, keys::KeysService}; + +#[derive(Debug, Clone)] +pub struct BaseCtx { + user: String, + caller: Option, + path: Path, + cancellation_token: CancellationToken, + start_at: Instant, + http: Client, + store: Arc, + cache: Arc, + keys: Arc, + depth: u8, + endpoint_identity: String, + endpoint_canister_query: String, + endpoint_canister_update: String, +} + +impl BaseCtx { + pub fn new(user: String, caller: Option, store: Arc) -> Self { + let http = Client::new(); + let keys = Arc::new(KeysService::new( + format!("{}/keys", TEE_LOCAL_SERVER), + http.clone(), + )); + let cache = Arc::new(CacheService::new(CACHE_MAX_CAPACITY)); + + Self { + user, + caller, + path: Path::default(), + cancellation_token: CancellationToken::new(), + start_at: Instant::now(), + http, + cache, + store, + keys, + depth: 0, + endpoint_identity: format!("{}/identity", TEE_LOCAL_SERVER), + endpoint_canister_query: format!("{}/canister/query", TEE_LOCAL_SERVER), + endpoint_canister_update: format!("{}/canister/update", TEE_LOCAL_SERVER), + } + } + + pub(crate) fn child(&self, path: String) -> Result { + let path = Path::parse(path)?; + let child = Self { + path, + cancellation_token: self.cancellation_token.child_token(), + depth: self.depth + 1, + ..self.clone() + }; + + if child.depth >= CONTEXT_MAX_DEPTH { + return Err("Context depth limit exceeded".into()); + } + Ok(child) + } + + pub(crate) fn child_with( + &self, + path: String, + user: String, + caller: Option, + ) -> Result { + let path = Path::parse(path)?; + let child = Self { + path, + user, + caller, + cancellation_token: self.cancellation_token.child_token(), + depth: self.depth + 1, + ..self.clone() + }; + + if child.depth >= CONTEXT_MAX_DEPTH { + return Err("Context depth limit exceeded".into()); + } + Ok(child) + } +} + +impl BaseContext for BaseCtx { + type Error = BoxError; + + fn user(&self) -> String { + self.user.clone() + } + + fn caller(&self) -> Option { + self.caller + } + + fn cancellation_token(&self) -> CancellationToken { + self.cancellation_token.clone() + } + + fn time_elapsed(&self) -> Duration { + self.start_at.elapsed() + } + + fn unix_ms() -> u64 { + unix_ms() + } + + /// Generates N random bytes + fn rand_bytes() -> [u8; N] { + rand_bytes() + } +} + +impl KeysFeatures for BaseCtx { + /// Derives a 256-bit AES-GCM key from the given derivation path + async fn a256gcm_key(&self, derivation_path: &[&[u8]]) -> Result<[u8; 32], BoxError> { + self.keys.a256gcm_key(&self.path, derivation_path).await + } + + /// Signs a message using Ed25519 signature scheme from the given derivation path + async fn ed25519_sign_message( + &self, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + self.keys + .ed25519_sign_message(&self.path, derivation_path, message) + .await + } + + /// Verifies an Ed25519 signature from the given derivation path + async fn ed25519_verify( + &self, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + self.keys + .ed25519_verify(&self.path, derivation_path, message, signature) + .await + } + + /// Gets the public key for Ed25519 from the given derivation path + async fn ed25519_public_key(&self, derivation_path: &[&[u8]]) -> Result<[u8; 32], BoxError> { + self.keys + .ed25519_public_key(&self.path, derivation_path) + .await + } + + /// Signs a message using Secp256k1 BIP340 Schnorr signature from the given derivation path + async fn secp256k1_sign_message_bip340( + &self, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + self.keys + .secp256k1_sign_message_bip340(&self.path, derivation_path, message) + .await + } + + /// Verifies a Secp256k1 BIP340 Schnorr signature from the given derivation path + async fn secp256k1_verify_bip340( + &self, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + self.keys + .secp256k1_verify_bip340(&self.path, derivation_path, message, signature) + .await + } + + /// Signs a message using Secp256k1 ECDSA signature from the given derivation path + async fn secp256k1_sign_message_ecdsa( + &self, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + self.keys + .secp256k1_sign_message_ecdsa(&self.path, derivation_path, message) + .await + } + + /// Verifies a Secp256k1 ECDSA signature from the given derivation path + async fn secp256k1_verify_ecdsa( + &self, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + self.keys + .secp256k1_verify_ecdsa(&self.path, derivation_path, message, signature) + .await + } + + /// Gets the compressed SEC1-encoded public key for Secp256k1 from the given derivation path + async fn secp256k1_public_key(&self, derivation_path: &[&[u8]]) -> Result<[u8; 33], BoxError> { + self.keys + .secp256k1_public_key(&self.path, derivation_path) + .await + } +} + +impl StoreFeatures for BaseCtx { + /// Retrieves data from storage at the specified path + async fn store_get(&self, path: &Path) -> Result<(bytes::Bytes, ObjectMeta), BoxError> { + let res = self.store.get_opts(path, Default::default()).await?; + let data = match res.payload { + object_store::GetResultPayload::Stream(mut stream) => { + let mut buf = bytes::BytesMut::new(); + while let Some(data) = stream.try_next().await? { + buf.extend_from_slice(&data); + } + buf.freeze() // Convert to immutable Bytes + } + _ => return Err("StoreFeatures: unexpected payload from get_opts".into()), + }; + Ok((data, res.meta)) + } + + /// Lists objects in storage with optional prefix and offset filters + /// + /// # Arguments + /// * `prefix` - Optional path prefix to filter results + /// * `offset` - Optional path to start listing from (exclude) + async fn store_list( + &self, + prefix: Option<&Path>, + offset: &Path, + ) -> Result, BoxError> { + let mut res = self.store.list_with_offset(prefix, offset); + let mut metas = Vec::new(); + while let Some(meta) = res.try_next().await? { + metas.push(meta) + } + + Ok(metas) + } + + /// Stores data at the specified path with a given write mode + /// + /// # Arguments + /// * `path` - Target storage path + /// * `mode` - Write mode (Create, Overwrite, etc.) + /// * `val` - Data to store as bytes + async fn store_put( + &self, + path: &Path, + mode: PutMode, + val: bytes::Bytes, + ) -> Result { + let res = self + .store + .put_opts( + path, + val.into(), + PutOptions { + mode, + ..Default::default() + }, + ) + .await?; + Ok(res) + } + + /// Renames a storage object if the target path doesn't exist + /// + /// # Arguments + /// * `from` - Source path + /// * `to` - Destination path + async fn store_rename_if_not_exists(&self, from: &Path, to: &Path) -> Result<(), BoxError> { + self.store.rename_if_not_exists(from, to).await?; + Ok(()) + } + + /// Deletes data at the specified path + /// + /// # Arguments + /// * `path` - Path of the object to delete + async fn store_delete(&self, path: &Path) -> Result<(), BoxError> { + self.store.delete(path).await?; + Ok(()) + } +} + +impl CacheFeatures for BaseCtx { + /// Checks if a key exists in the cache + fn cache_contains(&self, key: &str) -> bool { + self.cache.cache_contains(&self.path, key) + } + + /// Gets a cached value by key, returns error if not found or deserialization fails + async fn cache_get(&self, key: &str) -> Result + where + T: DeserializeOwned, + { + self.cache.cache_get(&self.path, key).await + } + + /// Gets a cached value or initializes it if missing + /// + /// If key doesn't exist, calls init function to create value and cache it + async fn cache_get_with(&self, key: &str, init: F) -> Result + where + T: Sized + DeserializeOwned + Serialize + Send, + F: Future), BoxError>> + Send + 'static, + { + self.cache.cache_get_with(&self.path, key, init).await + } + + /// Sets a value in cache with optional expiration policy + async fn cache_set(&self, key: &str, val: (T, Option)) + where + T: Sized + Serialize + Send, + { + self.cache.cache_set(&self.path, key, val).await + } + + /// Deletes a cached value by key, returns true if key existed + async fn cache_delete(&self, key: &str) -> bool { + self.cache.cache_delete(&self.path, key).await + } +} + +impl CanisterFeatures for BaseCtx { + /// Performs a query call to a canister (read-only, no state changes) + /// + /// # Arguments + /// * `canister` - Target canister principal + /// * `method` - Method name to call + /// * `args` - Input arguments encoded in Candid format + async fn canister_query< + In: ArgumentEncoder + Send, + Out: CandidType + for<'a> candid::Deserialize<'a>, + >( + &self, + canister: &Principal, + method: &str, + args: In, + ) -> Result { + let res = canister_rpc( + &self.http, + &self.endpoint_canister_query, + canister, + method, + args, + ) + .await?; + Ok(res) + } + + /// Performs an update call to a canister (may modify state) + /// + /// # Arguments + /// * `canister` - Target canister principal + /// * `method` - Method name to call + /// * `args` - Input arguments encoded in Candid format + async fn canister_update< + In: ArgumentEncoder + Send, + Out: CandidType + for<'a> candid::Deserialize<'a>, + >( + &self, + canister: &Principal, + method: &str, + args: In, + ) -> Result { + let res = canister_rpc( + &self.http, + &self.endpoint_canister_update, + canister, + method, + args, + ) + .await?; + Ok(res) + } +} + +impl HttpFeatures for BaseCtx { + /// Makes an HTTPs request + /// + /// # Arguments + /// * `url` - Target URL, should start with `https://` + /// * `method` - HTTP method (GET, POST, etc.) + /// * `headers` - Optional HTTP headers + /// * `body` - Optional request body (default empty) + async fn https_call( + &self, + url: &str, + method: http::Method, + headers: Option, + body: Option>, // default is empty + ) -> Result { + if !url.starts_with("https://") { + return Err("Invalid URL, must start with https://".into()); + } + let mut req = self.http.request(method, url); + if let Some(headers) = headers { + req = req.headers(headers); + } + if let Some(body) = body { + req = req.body(body); + } + + req.send().await.map_err(|e| e.into()) + } + + /// Makes a signed HTTPs request with message authentication + /// + /// # Arguments + /// * `url` - Target URL + /// * `method` - HTTP method (GET, POST, etc.) + /// * `message_digest` - 32-byte message digest for signing + /// * `headers` - Optional HTTP headers + /// * `body` - Optional request body (default empty) + async fn https_signed_call( + &self, + url: &str, + method: http::Method, + message_digest: &[u8; 32], + headers: Option, + body: Option>, // default is empty + ) -> Result { + let res: HashMap = http_rpc( + &self.http, + &self.endpoint_identity, + "sign_http", + &(message_digest,), + ) + .await?; + let mut headers = headers.unwrap_or_default(); + res.into_iter().for_each(|(k, v)| { + headers.insert( + http::HeaderName::try_from(k).expect("invalid header name"), + http::HeaderValue::try_from(v).expect("invalid header value"), + ); + }); + self.https_call(url, method, Some(headers), body).await + } + + /// Makes a signed CBOR-encoded RPC call + /// + /// # Arguments + /// * `endpoint` - URL endpoint to send the request to + /// * `method` - RPC method name to call + /// * `params` - Parameters to serialize as CBOR and send with the request + async fn https_signed_rpc( + &self, + endpoint: &str, + method: &str, + params: impl Serialize + Send, + ) -> Result + where + T: DeserializeOwned, + { + let params = to_cbor_bytes(¶ms); + let req = RPCRequest { + method, + params: ¶ms.into(), + }; + let body = to_cbor_bytes(&req); + let digest: [u8; 32] = sha3_256(&body); + let res: HashMap = + http_rpc(&self.http, &self.endpoint_identity, "sign_http", &(digest,)).await?; + let mut headers = http::HeaderMap::new(); + res.into_iter().for_each(|(k, v)| { + headers.insert( + http::HeaderName::try_from(k).expect("invalid header name"), + http::HeaderValue::try_from(v).expect("invalid header value"), + ); + }); + + let res = cbor_rpc(&self.http, endpoint, method, Some(headers), body).await?; + let res = from_reader(&res[..]).map_err(|e| HttpRPCError::ResultError { + endpoint: endpoint.to_string(), + path: method.to_string(), + error: e.into(), + })?; + Ok(res) + } +} diff --git a/anda_engine/src/context/cache.rs b/anda_engine/src/context/cache.rs new file mode 100644 index 0000000..81bb737 --- /dev/null +++ b/anda_engine/src/context/cache.rs @@ -0,0 +1,139 @@ +use anda_core::context::CacheExpiry; +use anda_core::BoxError; +use bytes::Bytes; +use ciborium::from_reader; +use ic_cose_types::to_cbor_bytes; +use moka::{future::Cache, policy::Expiry}; +use object_store::path::Path; +use serde::{de::DeserializeOwned, Serialize}; +use std::{ + future::Future, + time::{Duration, Instant}, +}; + +#[derive(Debug)] +pub struct CacheService { + cache: Cache)>, +} + +impl CacheService { + pub fn new(max_capacity: u64) -> Self { + let expire = CacheServiceExpiry; + Self { + cache: Cache::builder() + .max_capacity(max_capacity) + // max TTI is 7 days + .time_to_idle(Duration::from_secs(3600 * 24 * 7)) + .expire_after(expire) + .build(), + } + } +} + +impl CacheService { + /// Checks if a key exists in the cache + pub fn cache_contains(&self, path: &Path, key: &str) -> bool { + self.cache.contains_key(path.child(key).as_ref()) + } + + /// Gets a cached value by key, returns error if not found or deserialization fails + pub async fn cache_get(&self, path: &Path, key: &str) -> Result + where + T: DeserializeOwned, + { + if let Some((bytes, _)) = self.cache.get(path.child(key).as_ref()).await { + from_reader(&bytes[..]).map_err(|e| e.into()) + } else { + Err(format!("Key {} not found", key).into()) + } + } + + /// Gets a cached value or initializes it if missing + /// + /// If key doesn't exist, calls init function to create value and cache it + pub async fn cache_get_with(&self, path: &Path, key: &str, init: F) -> Result + where + T: Sized + DeserializeOwned + Serialize + Send, + F: Future), BoxError>> + Send + 'static, + { + futures_util::pin_mut!(init); + match self + .cache + .try_get_with(path.child(key).into(), async move { + match init.await { + Ok((val, expiry)) => { + let data = to_cbor_bytes(&val); + Ok((data.into(), expiry)) + } + Err(e) => Err(e), + } + }) + .await + { + Ok((bytes, _)) => from_reader(&bytes[..]).map_err(|e| e.into()), + Err(e) => Err(format!("Key {} init failed: {}", key, e).into()), + } + } + + /// Sets a value in cache with optional expiration policy + pub async fn cache_set(&self, path: &Path, key: &str, val: (T, Option)) + where + T: Sized + Serialize + Send, + { + let data = to_cbor_bytes(&val.0); + self.cache + .insert(path.child(key).into(), (data.into(), val.1)) + .await; + } + + /// Deletes a cached value by key, returns true if key existed + pub async fn cache_delete(&self, path: &Path, key: &str) -> bool { + self.cache.remove(path.child(key).as_ref()).await.is_some() + } +} + +struct CacheServiceExpiry; + +impl Expiry)> for CacheServiceExpiry { + fn expire_after_create( + &self, + _key: &String, + value: &(Bytes, Option), + _created_at: Instant, + ) -> Option { + match value.1 { + Some(CacheExpiry::TTL(du)) => Some(du), + Some(CacheExpiry::TTI(du)) => Some(du), + None => None, + } + } + + fn expire_after_read( + &self, + _key: &String, + value: &(Bytes, Option), + _read_at: Instant, + duration_until_expiry: Option, + _last_modified_at: Instant, + ) -> Option { + match value.1 { + Some(CacheExpiry::TTL(_)) => duration_until_expiry, + Some(CacheExpiry::TTI(du)) => Some(du), + None => None, + } + } + + fn expire_after_update( + &self, + _key: &String, + value: &(Bytes, Option), + _updated_at: Instant, + _duration_until_expiry: Option, + ) -> Option { + match value.1 { + Some(CacheExpiry::TTL(du)) => Some(du), + Some(CacheExpiry::TTI(du)) => Some(du), + None => None, + } + } +} diff --git a/anda_engine/src/context/keys.rs b/anda_engine/src/context/keys.rs new file mode 100644 index 0000000..d84d8e7 --- /dev/null +++ b/anda_engine/src/context/keys.rs @@ -0,0 +1,158 @@ +use anda_core::{http_rpc, BoxError}; +use ic_cose_types::cose::ed25519::ed25519_verify; +use ic_cose_types::cose::k256::{secp256k1_verify_bip340, secp256k1_verify_ecdsa}; +use object_store::path::Path; +use reqwest::Client; +use serde_bytes::ByteArray; + +#[derive(Debug, Clone)] +pub struct KeysService { + endpoint: String, + http: Client, +} + +impl KeysService { + pub fn new(endpoint: String, http: Client) -> Self { + Self { endpoint, http } + } +} + +impl KeysService { + /// Derives a 256-bit AES-GCM key from the given derivation path + pub async fn a256gcm_key( + &self, + path: &Path, + derivation_path: &[&[u8]], + ) -> Result<[u8; 32], BoxError> { + let mut dp = Vec::with_capacity(derivation_path.len() + 1); + dp.push(path.as_ref().as_bytes()); + dp.extend(derivation_path); + let res: ByteArray<32> = + http_rpc(&self.http, &self.endpoint, "a256gcm_key", &(dp,)).await?; + Ok(res.into_array()) + } + + /// Signs a message using Ed25519 signature scheme from the given derivation path + pub async fn ed25519_sign_message( + &self, + path: &Path, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + let mut dp = Vec::with_capacity(derivation_path.len() + 1); + dp.push(path.as_ref().as_bytes()); + dp.extend(derivation_path); + let res: ByteArray<64> = http_rpc( + &self.http, + &self.endpoint, + "ed25519_sign_message", + &(dp, message), + ) + .await?; + Ok(res.into_array()) + } + + /// Verifies an Ed25519 signature from the given derivation path + pub async fn ed25519_verify( + &self, + path: &Path, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + let pk = self.ed25519_public_key(path, derivation_path).await?; + ed25519_verify(&pk, message, signature).map_err(|e| e.into()) + } + + /// Gets the public key for Ed25519 from the given derivation path + pub async fn ed25519_public_key( + &self, + path: &Path, + derivation_path: &[&[u8]], + ) -> Result<[u8; 32], BoxError> { + let mut dp = Vec::with_capacity(derivation_path.len() + 1); + dp.push(path.as_ref().as_bytes()); + dp.extend(derivation_path); + let res: (ByteArray<32>, ByteArray<32>) = + http_rpc(&self.http, &self.endpoint, "ed25519_public_key", &(dp,)).await?; + Ok(res.0.into_array()) + } + + /// Signs a message using Secp256k1 BIP340 Schnorr signature from the given derivation path + pub async fn secp256k1_sign_message_bip340( + &self, + path: &Path, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + let mut dp = Vec::with_capacity(derivation_path.len() + 1); + dp.push(path.as_ref().as_bytes()); + dp.extend(derivation_path); + let res: ByteArray<64> = http_rpc( + &self.http, + &self.endpoint, + "secp256k1_sign_message_bip340", + &(dp, message), + ) + .await?; + Ok(res.into_array()) + } + + /// Verifies a Secp256k1 BIP340 Schnorr signature from the given derivation path + pub async fn secp256k1_verify_bip340( + &self, + path: &Path, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + let pk = self.secp256k1_public_key(path, derivation_path).await?; + secp256k1_verify_bip340(&pk, message, signature).map_err(|e| e.into()) + } + + /// Signs a message using Secp256k1 ECDSA signature from the given derivation path + pub async fn secp256k1_sign_message_ecdsa( + &self, + path: &Path, + derivation_path: &[&[u8]], + message: &[u8], + ) -> Result<[u8; 64], BoxError> { + let mut dp = Vec::with_capacity(derivation_path.len() + 1); + dp.push(path.as_ref().as_bytes()); + dp.extend(derivation_path); + let res: ByteArray<64> = http_rpc( + &self.http, + &self.endpoint, + "secp256k1_sign_message_ecdsa", + &(dp, message), + ) + .await?; + Ok(res.into_array()) + } + + /// Verifies a Secp256k1 ECDSA signature from the given derivation path + pub async fn secp256k1_verify_ecdsa( + &self, + path: &Path, + derivation_path: &[&[u8]], + message: &[u8], + signature: &[u8], + ) -> Result<(), BoxError> { + let pk = self.secp256k1_public_key(path, derivation_path).await?; + secp256k1_verify_ecdsa(&pk, message, signature).map_err(|e| e.into()) + } + + /// Gets the compressed SEC1-encoded public key for Secp256k1 from the given derivation path + pub async fn secp256k1_public_key( + &self, + path: &Path, + derivation_path: &[&[u8]], + ) -> Result<[u8; 33], BoxError> { + let mut dp = Vec::with_capacity(derivation_path.len() + 1); + dp.push(path.as_ref().as_bytes()); + dp.extend(derivation_path); + let res: (ByteArray<33>, ByteArray<32>) = + http_rpc(&self.http, &self.endpoint, "secp256k1_public_key", &(dp,)).await?; + Ok(res.0.into_array()) + } +} diff --git a/anda_engine/src/context/mod.rs b/anda_engine/src/context/mod.rs new file mode 100644 index 0000000..da1cc9c --- /dev/null +++ b/anda_engine/src/context/mod.rs @@ -0,0 +1,9 @@ +mod agent; +mod base; +mod cache; +mod keys; + +pub use agent::*; +pub use base::*; +pub use cache::*; +pub use keys::*; diff --git a/anda_engine/src/engine.rs b/anda_engine/src/engine.rs new file mode 100644 index 0000000..a344653 --- /dev/null +++ b/anda_engine/src/engine.rs @@ -0,0 +1,118 @@ +use anda_core::{AgentOutput, AgentSet, BoxError, ToolSet}; +use candid::Principal; +use ic_cose_types::validate_str; +use object_store::ObjectStore; +use serde_json::Value; +use std::sync::Arc; + +use crate::context::{AgentCtx, BaseCtx}; + +pub struct Engine { + ctx: AgentCtx, + tools: ToolSet, + agents: AgentSet, + store: Arc, + default_agent: String, +} + +impl Engine { + pub async fn agent_run( + &self, + user: String, + caller: Option, + prompt: String, + attachment: Option, + agent_name: Option, + ) -> Result { + let name = agent_name.unwrap_or(self.default_agent.clone()); + if !self.agents.contains(&name) { + return Err(format!("agent {} not found", name).into()); + } + + let ctx = self.ctx.child_with(&name, user, caller)?; + self.ctx.agents.run(&name, &ctx, &prompt, attachment).await + } + + pub async fn tool_call( + &self, + user: String, + caller: Option, + name: String, + args: Value, + ) -> Result { + if !self.tools.contains(&name) { + return Err(format!("tool {} not found", name).into()); + } + + let ctx = self.ctx.child_base_with(&name, user, caller)?; + self.ctx.tools.call(&name, &ctx, &args).await + } +} + +pub struct EngineBuilder { + tools: ToolSet, + agents: AgentSet, + store: Option>, + default_agent: String, +} + +impl Default for EngineBuilder { + fn default() -> Self { + Self::new() + } +} + +impl EngineBuilder { + pub fn new() -> Self { + EngineBuilder { + tools: ToolSet::new(), + agents: AgentSet::new(), + store: None, + default_agent: "default".to_string(), + } + } + + pub fn with_default_agent(mut self, name: String) -> Self { + self.default_agent = name; + self + } + + pub fn with_store(mut self, store: Box) -> Self { + self.store = Some(store); + self + } + + pub fn register_tools(&mut self, tools: ToolSet) -> Result<(), BoxError> { + for (name, tool) in tools.set { + validate_str(&name)?; + if self.tools.contains(&name) { + return Err(format!("tool {} already exists", name).into()); + } + self.tools.set.insert(name, tool); + } + + Ok(()) + } + + pub fn register_agents(&mut self, agents: AgentSet) -> Result<(), BoxError> { + for (name, agent) in agents.set { + validate_str(&name)?; + if self.agents.contains(&name) { + return Err(format!("agent {} already exists", name).into()); + } + self.agents.set.insert(name, agent); + } + + Ok(()) + } + + // pub fn build(self) -> Engine { + // Engine { + // ctx: AgentCtx::new(), + // tools: self.tools, + // agents: self.agents, + // store: self.store, + // default_agent: self.default_agent, + // } + // } +} diff --git a/anda_engine/src/lib.rs b/anda_engine/src/lib.rs new file mode 100644 index 0000000..4e8636d --- /dev/null +++ b/anda_engine/src/lib.rs @@ -0,0 +1,2 @@ +pub mod context; +pub mod engine;