diff --git a/Cargo.toml b/Cargo.toml index 85c7e5b..8692599 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" exclude = [ + "hives/apple-fm", "hives/claude-cli", "hives/claude-repl", "hives/grpc", diff --git a/hives/README.md b/hives/README.md index 778b9c5..d2b020e 100644 --- a/hives/README.md +++ b/hives/README.md @@ -20,6 +20,7 @@ A honeybee on the plant pot by your bed is still a bee; you just need to know wh | [`claude-cli`](claude-cli) | nurse | `claude-cli-nurse` | `claude -p` stream-json over a pipe | | [`claude-repl`](claude-repl) | nurse | `claude-repl-nurse` | the `claude` REPL over a PTY | | [`dwarfstar`](dwarfstar) | nurse, one bee per model | `dwarfstar-bee` | the DwarfStar engine (`antirez/ds4`) linked into the bee, on Metal | +| [`apple-fm`](apple-fm) | nurse | `apple-fm-nurse` | Apple's on-device foundation model, through `apple-fm-gen`, a Swift bridge to FoundationModels | | [`ollama-server`](ollama-server) | forager, nurse | `ollama-server`, `ollama-nurse` | Ollama `/api/chat` and `/api/generate`, both ways | | [`openai-server`](openai-server) | forager, nurse, attendant | `node dist/index.js` | OpenAI `/v1/chat/completions`, `/v1/responses`, `/v1/completions`, `/v1/embeddings` in front, an OpenAI-compatible API behind | | [`anthropic-server`](anthropic-server) | forager | `node dist/index.js` | Anthropic `/v1/messages`, streamed or not | diff --git a/hives/apple-fm/.gitignore b/hives/apple-fm/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/hives/apple-fm/.gitignore @@ -0,0 +1 @@ +/target diff --git a/hives/apple-fm/Cargo.lock b/hives/apple-fm/Cargo.lock new file mode 100644 index 0000000..ce5a9b6 --- /dev/null +++ b/hives/apple-fm/Cargo.lock @@ -0,0 +1,5050 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "apple-fm" +version = "0.34.1" +dependencies = [ + "anyhow", + "async-trait", + "hum-identity", + "hum-nest", + "hum-paths", + "hum-thrum", + "humd", + "serde", + "serde_json", + "tempfile", + "thrum-core", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "arc-swap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64 0.22.1", + "http", + "log", + "url", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048006790404103c488e6c5616b3e7e702a71292aadda08e8f7698a9e0d2eb53" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bfbe32ac36b3c428696ce315464181bc4029d689fd58e3f2a4e58c252242f52" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "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 = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.1", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.21", +] + +[[package]] +name = "codegen" +version = "0.34.1" +dependencies = [ + "anyhow", + "regex", + "syn 2.0.119", +] + +[[package]] +name = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "command-group" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68fa787550392a9d58f44c21a3022cfb3ea3e2458b7f85d3b399d0ceeccf409" +dependencies = [ + "async-trait", + "nix", + "tokio", + "winapi", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cordyceps" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rand_core 0.10.1", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "data-encoding-macro" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a127ecbb3c4632e1525380e04c0c3fcf8dcb44d32a79ea290d8a36906edcd8" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54e03a951783e8b327515db3f2a2fd0e3bed362a96b066f341ce66ed49b4ead" +dependencies = [ + "data-encoding", + "syn 3.0.9", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004" +dependencies = [ + "const-oid 0.10.2", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "diatomic-waker" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8 0.11.0", + "serdect", + "signature 3.0.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519 2.2.3", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek 5.0.0", + "ed25519 3.0.0", + "rand_core 0.10.1", + "serde", + "sha2 0.11.1", + "signature 3.0.0", + "subtle", + "zeroize", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "ensemble" +version = "0.34.1" +dependencies = [ + "anyhow", + "async-trait", + "ed25519-dalek 2.2.0", + "hex", + "hum-identity", + "iroh", + "lru 0.12.5", + "parking_lot", + "rand 0.8.8", + "rustls", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 1.0.69", + "thrum-core", + "tokio", + "tokio-rustls", + "tracing", +] + +[[package]] +name = "enum-assoc" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0590c4a94da3372e83493b956755a6e2266830b6e4e3b101afe66e3f39477b91" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fancy-regex" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fraction" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-buffered" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be81b31c9ce4caf7f3178eeb90662bb1864d3ad363ec6bdada3201adff19c37" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result 0.4.1", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "governor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand 0.8.8", + "smallvec", + "spinning_top", +] + +[[package]] +name = "h2" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hum-identity" +version = "0.34.1" +dependencies = [ + "anyhow", + "ed25519-dalek 2.2.0", + "hex", + "hum-paths", + "rand 0.8.8", + "serde", + "serde_json", + "sha2 0.10.9", + "tracing", + "uuid", +] + +[[package]] +name = "hum-nest" +version = "0.34.1" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "command-group", + "hum-identity", + "hum-paths", + "hum-thrum", + "libc", + "lru 0.12.5", + "metrics", + "parking_lot", + "rand 0.8.8", + "regex", + "serde", + "serde_json", + "sysinfo", + "thrum-core", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hum-paths" +version = "0.34.1" +dependencies = [ + "jsonschema", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "hum-thrum" +version = "0.34.1" +dependencies = [ + "anyhow", + "hum-identity", + "hum-paths", + "rand 0.8.8", + "serde", + "serde_json", + "thrum-core", + "tokio", + "tracing", +] + +[[package]] +name = "humd" +version = "0.34.1" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "ed25519-dalek 2.2.0", + "ensemble", + "governor", + "hex", + "hum-identity", + "hum-nest", + "hum-paths", + "metrics", + "metrics-exporter-prometheus", + "parking_lot", + "rand 0.8.8", + "serde", + "serde_json", + "thehum", + "thrum-core", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" + +[[package]] +name = "icu_properties" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" + +[[package]] +name = "icu_provider" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "identity-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "igd-next" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7238d487a9aff61f81b5ab41c0a841532a115a398b5fa92a2fadd0885e2581" +dependencies = [ + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.10.3", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "iroh" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3689cab508af9bcef7816c1201b403595ec6aa20e7d15e1f9683bb4be4a5b17" +dependencies = [ + "backon", + "blake3", + "bytes", + "cfg_aliases", + "ctutils", + "data-encoding", + "derive_more", + "ed25519-dalek 3.0.0", + "futures-util", + "getrandom 0.4.3", + "http", + "ipnet", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "iroh-relay", + "n0-error", + "n0-future", + "n0-watcher", + "netwatch", + "noq", + "noq-proto", + "noq-udp", + "papaya", + "pin-project", + "portable-atomic", + "portmapper", + "rand 0.10.3", + "reqwest", + "rustc-hash", + "rustls", + "rustls-pki-types", + "serde", + "smallvec", + "strum 0.28.0", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", +] + +[[package]] +name = "iroh-base" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b72366b16001af3a51574233df88ca6d62da0215807549a769e7de887da803" +dependencies = [ + "curve25519-dalek 5.0.0", + "data-encoding", + "data-encoding-macro", + "derive_more", + "ed25519-dalek 3.0.0", + "getrandom 0.4.3", + "n0-error", + "rand 0.10.3", + "serde", + "url", + "zeroize", +] + +[[package]] +name = "iroh-dns" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7481d0792dde1035a2b06ec06ed3cf6d01fcb1a494e6931b489e32073382048d" +dependencies = [ + "arc-swap", + "cfg_aliases", + "derive_more", + "iroh-base", + "n0-dns-resolver", + "n0-error", + "n0-future", + "portable-atomic", + "rand 0.10.3", + "rustls", + "simple-dns", + "strum 0.28.0", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "iroh-metrics" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ede55536349842337f7cdc63012ec33dc637945f3259c98a0763aac364cdf09" +dependencies = [ + "iroh-metrics-derive", + "itoa", + "n0-error", + "portable-atomic", + "ryu", + "serde", + "tracing", +] + +[[package]] +name = "iroh-metrics-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae5f0c4405d1fbc9fb16ff422ca40620e93dc36c30ecaba0c2aee3992b7bd48" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "iroh-relay" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5589e7c1475700686603a970275687f760df853bc6525c7eb1347aa31d7230" +dependencies = [ + "blake3", + "bytes", + "cfg_aliases", + "data-encoding", + "derive_more", + "getrandom 0.4.3", + "http", + "http-body-util", + "hyper", + "hyper-util", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "lru 0.18.5", + "n0-error", + "n0-future", + "noq", + "noq-proto", + "num_enum", + "percent-encoding", + "pin-project", + "postcard", + "rand 0.10.3", + "reqwest", + "rustls", + "rustls-pki-types", + "serde", + "serde_bytes", + "strum 0.28.0", + "tokio", + "tokio-rustls", + "tokio-util", + "tokio-websockets", + "tracing", + "url", + "webpki-roots", + "ws_stream_wasm", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "jsonschema" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a699d3e77675e6aa4bfffe3b907c8b5f7ed3241f9965bffb25475ad4b08d05" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex", + "fraction", + "getrandom 0.3.4", + "idna", + "itoa", + "jsonschema-regex", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "reqwest", + "rustls", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd1086b01b9349fd4ef9a07433965af64c8ce8159abe633a189e4ff817bd13" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[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 = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5" +dependencies = [ + "hashbrown 0.17.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "mac-addr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "metrics" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3045b4193fbdc5b5681f32f11070da9be3609f189a79f3390706d42587f46bb5" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-util", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.14.5", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] + +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "n0-dns-resolver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535c99c9a786bee714155d2ee4fc2477405c2c43536e1bc6a51de09a0d1448f7" +dependencies = [ + "derive_more", + "ipconfig", + "jni 0.22.4", + "lru 0.18.5", + "n0-error", + "n0-future", + "ndk-context", + "rand 0.10.3", + "reqwest", + "rustc-hash", + "rustls", + "simple-dns", + "system-configuration", + "tokio", + "tokio-rustls", + "tracing", + "webpki-roots", +] + +[[package]] +name = "n0-error" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c59ea174675ce6bc196878851e5049e11b8b1f9af3ba87e4d6df8d828bb001" +dependencies = [ + "n0-error-macros", + "spez", +] + +[[package]] +name = "n0-error-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4412346410d6616f3668c40e53c298e5320c7b856f7a960e5914e442601be79f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "n0-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" +dependencies = [ + "cfg_aliases", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "n0-watcher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bf1ecb076a87e0e3f5edcf726e7d29e788c273bb0161ba30666b53f085906c" +dependencies = [ + "derive_more", + "n0-error", + "n0-future", + "slotmap", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "netdev" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "096c44b66a9b09b99b4dd36b1c295ff3a492039ccecaf55466bd6ddcdba59968" +dependencies = [ + "block2", + "dispatch2", + "dlopen2", + "ipnet", + "jni 0.21.1", + "libc", + "mac-addr", + "ndk-context", + "netlink-packet-core 0.8.2", + "netlink-packet-route 0.31.0", + "netlink-sys 0.8.8", + "objc2", + "objc2-core-foundation", + "objc2-core-wlan", + "objc2-foundation", + "objc2-system-configuration", + "once_cell", + "plist", + "windows-sys 0.61.2", +] + +[[package]] +name = "netdev" +version = "0.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c52c2584961c68f5a6e3356797344061f818c93b8acc9cd6d7e284795e563e" +dependencies = [ + "block2", + "dispatch2", + "dlopen2", + "ipnet", + "jni 0.21.1", + "libc", + "mac-addr", + "ndk-context", + "netlink-packet-core 0.9.0", + "netlink-packet-route 0.33.0", + "netlink-sys 0.9.0", + "objc2-core-foundation", + "objc2-system-configuration", + "once_cell", + "plist", + "windows-sys 0.61.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b897d7bd4f0af82e68d40d0344cf37e97f9c97ddf74a098de3e4da05e96ca395" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6e2daf9a8c2e21302714706860a0e6be02e03d17294ecc66b354ea7d4059dd" + +[[package]] +name = "netlink-packet-route" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2288fcb784eb3defd5fb16f4c4160d5f477de192eac730f43e1d11c24d9a007" +dependencies = [ + "bitflags", + "libc", + "log", + "netlink-packet-core 0.8.2", +] + +[[package]] +name = "netlink-packet-route" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d48ffc8b73a506e1ff0878528c72668f2bfbc3d875b6be890a96be5d0d5576" +dependencies = [ + "bitflags", + "libc", + "log", + "netlink-packet-core 0.9.0", + "zerocopy", +] + +[[package]] +name = "netlink-proto" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93af8261786086024cd5e96e0a991dd65ced07bbf7c233a487bbc96b971d5539" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "log", + "netlink-packet-core 0.8.2", + "netlink-sys 0.8.8", + "thiserror 2.0.21", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99d38e00b420df49e940fe0826e667c3dad82ac68eb4c2bbf754c1c14a83a10" +dependencies = [ + "bytes", + "libc", + "log", +] + +[[package]] +name = "netwatch" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39da9cad5f23aa43401f09497d3b280e51b5feba115d9ffbf38ca35d6ff95e96" +dependencies = [ + "atomic-waker", + "bytes", + "cfg_aliases", + "derive_more", + "ipnet", + "js-sys", + "libc", + "n0-error", + "n0-future", + "n0-watcher", + "netdev 0.45.1", + "netdev 0.46.3", + "netlink-packet-core 0.8.2", + "netlink-packet-route 0.31.0", + "netlink-proto", + "netlink-sys 0.8.8", + "noq-udp", + "objc2-core-foundation", + "objc2-system-configuration", + "pin-project-lite", + "serde", + "socket2", + "time", + "tokio", + "tokio-util", + "tracing", + "web-sys", + "windows 0.62.2", + "windows-result 0.4.1", + "wmi", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + +[[package]] +name = "noq" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6249a065adc1edbc1704a95436f38dd60476064dbc3b7c47cf279e02d1590eb5" +dependencies = [ + "bytes", + "cfg_aliases", + "derive_more", + "noq-proto", + "noq-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.21", + "tokio", + "tokio-stream", + "tracing", + "web-time", +] + +[[package]] +name = "noq-proto" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01a35be3ed37558c8db69554419f24eff8a63599b54f711bbe3863ec727c47d" +dependencies = [ + "aes-gcm", + "bytes", + "derive_more", + "enum-assoc", + "getrandom 0.4.3", + "identity-hash", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "sorted-index-buffer", + "thiserror 2.0.21", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "noq-udp" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d0160422832eea1cc11c34be04ee6d5a046452b0b46cfb14695d55a1f24c954" +dependencies = [ + "cfg_aliases", + "libc", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "block2", + "dispatch2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-core-wlan" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-security", + "objc2-security-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-security-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "bitflags", + "dispatch2", + "libc", + "objc2", + "objc2-core-foundation", + "objc2-security", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "papaya" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2442474a9404698c42509b8967f437249dbc7b50493e83020333d3943ec0ae" +dependencies = [ + "equivalent", + "seize", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.3", + "spki 0.8.1", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plist" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327a4cb2ad49dc48adfdc9da89e22c64d488b508c67a044d71ba729e4521a285" +dependencies = [ + "base64 0.23.1", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +dependencies = [ + "serde", +] + +[[package]] +name = "portmapper" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca97242f016e090a25330613bc2382aab65eb22f9149dbe84d8f8e711d49a530" +dependencies = [ + "base64 0.22.1", + "bytes", + "derive_more", + "hyper-util", + "igd-next", + "iroh-metrics", + "libc", + "n0-error", + "n0-future", + "netwatch", + "num_enum", + "rand 0.10.3", + "serde", + "smallvec", + "socket2", + "time", + "tokio", + "tokio-util", + "tower-layer", + "tracing", + "url", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "postcard-derive", + "serde", +] + +[[package]] +name = "postcard-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "referencing" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbf332a2f81899f6836f22c03da73dae8a664c32e3016b84692c23cddadc95d" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "seize" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[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 = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[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 = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple-dns" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6f884fa9a8d48101774bfbd3aeb81e968dd22cffd19a372da69f183db22c1a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sorted-index-buffer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" + +[[package]] +name = "spez" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + +[[package]] +name = "spinning_top" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf02766a1eb909bc7653557c66b384e44edc3ad979e6257c6173c65ea51bc7d" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9" +dependencies = [ + "base64ct", + "der 0.8.3", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "sysinfo" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501336eb7ba9e417300a6a0fa985721065467aa83a6dcf0422a8e43e4c0328fa" +dependencies = [ + "bitflags", + "core-foundation", + "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 = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thehum" +version = "0.34.1" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "ed25519-dalek 2.2.0", + "ensemble", + "hex", + "hum-identity", + "hum-paths", + "parking_lot", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 1.0.69", + "thrum-core", + "tokio", + "tracing", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "thrum-core" +version = "0.34.1" +dependencies = [ + "codegen", + "hex", + "hum-identity", + "parking_lot", + "serde", + "serde_json", + "sha2 0.10.9", + "strum 0.26.3", + "thiserror 1.0.69", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "js-sys", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-websockets" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-sink", + "getrandom 0.4.3", + "http", + "httparse", + "rand 0.10.3", + "ring", + "rustls-pki-types", + "sha1_smol", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "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 = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0" +dependencies = [ + "utf_types", +] + +[[package]] +name = "utf_types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6" + +[[package]] +name = "uuid" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" +dependencies = [ + "js-sys", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" +dependencies = [ + "cfg-if", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88" +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-root-certs" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wmi" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 2.0.21", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper", + "thiserror 2.0.21", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "yoke" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/hives/apple-fm/Cargo.toml b/hives/apple-fm/Cargo.toml new file mode 100644 index 0000000..7d3382c --- /dev/null +++ b/hives/apple-fm/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "apple-fm" +version = "0.34.1" +edition = "2024" +license = "MIT" +description = "Apple's on-device foundation model as a hum nurse: local, network-free and key-free, through a small Swift bridge to FoundationModels." + +[dependencies] +thrum-core = { path = "../../thrum-core" } +hum-paths = { path = "../../hum-paths" } +hum-nest = { path = "../../hum-nest" } +tokio = { version = "1", features = ["full"] } +tokio-util = "0.7" +async-trait = "0.1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[dev-dependencies] +humd = { path = "../../humd" } +hum-identity = { path = "../../hum-identity" } +hum-thrum = { path = "../../hum-thrum" } +tempfile = "3" + +[[bin]] +name = "apple-fm-nurse" +path = "src/main.rs" +[workspace] diff --git a/hives/apple-fm/Orchfile b/hives/apple-fm/Orchfile new file mode 100644 index 0000000..0be6b45 --- /dev/null +++ b/hives/apple-fm/Orchfile @@ -0,0 +1,27 @@ +# apple-fm: Apple's on-device foundation model as a nurse, grown through +# apple-fm-gen, the Swift bridge ./build makes beside it. Set an ARG in +# the environment of `hum hive apple-fm install` to change it, for +# example APPLE_FM_MODEL=apple-on-device. An empty ARG keeps the bee's +# own default; the README says what each one is. +# +# The nurse waits unnestled while Apple Intelligence is off or its model +# is still downloading, and on a Mac that cannot run the model it exits +# 0, so its unit stays down; RESTART on-failure restarts it only when it +# fails. + +ARG HUM_SRC= +ARG APPLE_FM_MODEL= +ARG APPLE_FM_PROBE_MS= + +SERVICE hive-install +RUN ${HUM_SRC}/install +ONESHOT true + +SERVICE apple-fm +RUN ${HUM_SRC}/target/release/apple-fm-nurse +ENV APPLE_FM_MODEL=${APPLE_FM_MODEL} +ENV APPLE_FM_PROBE_MS=${APPLE_FM_PROBE_MS} +RESTART on-failure +RESTART_DELAY 5s +START_LIMIT_BURST 3 +START_LIMIT_INTERVAL 1m diff --git a/hives/apple-fm/README.md b/hives/apple-fm/README.md new file mode 100644 index 0000000..0ac412b --- /dev/null +++ b/hives/apple-fm/README.md @@ -0,0 +1,219 @@ +--- +title: "apple-fm (Rust + Swift)" +description: "Apple's on-device foundation model as a nurse: local, network-free and key-free, through a small Swift bridge to FoundationModels" +--- + +# apple-fm (Rust + Swift) + +> _Apple's on-device foundation model as a nurse: local, network-free and key-free, through a small Swift bridge to FoundationModels_ + +The hive has one bee, `apple-fm-nurse`, entered as a nurse under the +label `main`. It claims one model id, `apple-3b-on-device`, and grows +each bloom through the language model Apple ships with macOS 26 +(`SystemLanguageModel.default`, about 3B parameters, a 4096-token +context), on this Mac itself. Nothing leaves the +machine, and there is no key. + +The nurse is Rust, on [`hum-nest`](../../hum-nest). It reaches Apple +through `apple-fm-gen`, a Swift binary built from +[`bridge/gen.swift`](bridge/gen.swift) and run once per bloom. That one +file is the only code in hum that imports an Apple framework: the Rust +side holds no Apple symbol and builds anywhere, and the hive's `build` +refuses any host but an Apple silicon Mac. The hive is its own cargo +workspace, outside the repo root's. + +## Propensity + +| bee | statefulness | richness | wire | +|---|---|---|---| +| `apple-fm-nurse` | `stateful_session` | none claimed | `apple-fm` | + +It speaks what every hum-nest nurse does: `hello`, `prompt`, `cancel`, +`cleanup`, `curate`, `tendril-result`, `unfurl`, `wilt`, `error`, +`tendril-reach`, `tendril-info` and `pulse`. + +## The nurse + +| env | default | what | +|---|---|---| +| `APPLE_FM_MODEL` | `apple-3b-on-device` | the model id it claims | +| `APPLE_FM_LABEL` | `main` | the label its bee is entered as; a second nurse beside it needs its own | +| `APPLE_FM_BRIDGE` | `apple-fm-gen` beside the nurse's binary | the bridge it runs | +| `APPLE_FM_PROBE_MS` | `60000` | how often a waiting nurse asks again whether the model is ready | + +An empty `APPLE_FM_*` variable is no variable too. + +**Before it nestles** the nurse asks the bridge whether Apple's model +can run here (`apple-fm-gen --probe`). When it can, the nurse nestles. +While Apple Intelligence is off (System Settings → Apple Intelligence & +Siri) or its model is still downloading, it waits without nestling, +logs `apple-fm.dormant` with the reason once, and asks again every +`APPLE_FM_PROBE_MS`, nestling as soon as the model is ready. On a Mac +that cannot run the model at all it logs `apple-fm.never` and exits 0, +so its unit stays down. A bridge that cannot be run exits it non-zero. + +A bloom grows through one run of the bridge: + +- **The conversation.** The system prompt becomes Apple's instructions. + The blooms before the prompt become Apple's transcript, each bloom's + words as a prompt and the text it grew as the response, and the + prompt's own words are what the model answers. A prompt that carries + a graft is grown from its graft. One that carries none, as from a + forager that sends only a sigil's new words, is grown from the turns + its cell has grown so far, so a sigil keeps its conversation either + way. The model's context is 4096 tokens, so the bridge keeps the + newest turns that leave room for the reply (its `season`, else 1024 + tokens) and drops the older ones, and the cell forgets them too; the + nurse logs `apple-fm.trimmed`. +- **How it grows.** `temperature` and `season` become Apple's + `temperature` and `maximumResponseTokens`. A `temperature` of 0 + samples greedily; `prune.k` samples from the top k, else `prune.p` + from the top p, each with the prompt's `seed`; a `seed` alone samples + from them all. `frost` is kept by the nurse: the bloom unfurls up to + the first stop it meets and wilts there, and the bridge is killed. + `sepal` is ignored, since the model does not think aloud. +- **The reply.** Its words unfurl as a text petal as the model grows + them, each unfurl only the words new since the last. The bloom wilts + `stop`, or `max_tokens` when it grew all of its `season`, with Apple's + token counts as nectar. A reply is held to the context left after + the conversation, whatever its `season`, so a long one wilts + `max_tokens` rather than overflowing. A `cancel` kills the bridge and wilts it + `cancel`. (Counts, and the room the reply is held to, need macOS 26.4 + or later; before it the bridge drops the older half of the turns + whenever Apple refuses a conversation as too long.) +- **Refused.** Apple's model grows words from words, so a prompt whose + petals carry pollen is refused with `apple_fm_words_only`, and one + with a `trellis` with `apple_fm_no_trellis`. The nurse does not grow + on: a prompt with no words is refused by its nest with + `cannot-grow-on`, or `empty-prompt` when it carries no graft either. +- **Errors.** Apple's own become `apple_fm_`: + `apple_fm_exceeded_context_window` when the prompt alone overflows + the model's context, + `apple_fm_guardrail_violation`, `apple_fm_refusal`, + `apple_fm_unsupported_language`, `apple_fm_rate_limited`, + `apple_fm_assets_unavailable`, and `apple_fm_unavailable` when the + model stopped being ready after the nurse nestled. The nurse adds + `apple_fm_bridge_spawn` when the bridge cannot be run, + `apple_fm_bridge_died` when it ends without finishing (with its exit + status and the end of its stderr), and `apple_fm_cell_stilled` for a + prompt its cell was stilled before it grew. + +The nurse offers the model no tools, so its blooms reach no tendrils. + +Its nest's limits are in the `nest` section of +`$XDG_CONFIG_HOME/hum/hives/apple-fm.json`. A cell lives for its sigil, +remembering the turns it grew and growing each prompt the nest hands it +there in turn, until the nest ends it: past `maxCells` (default 8, and +the headroom the nurse claims) as the least recently used, after +`idleMs` (default five minutes) idle, or past `bounds.wallClockMs`. +However a cell ends, the bloom growing in it wilts `cancel` and its +bridge is killed. The other `bounds` cap nothing here. + +### The bridge + +`apple-fm-gen` reads one JSON line on stdin and writes NDJSON on +stdout, each line as soon as it is known: + +``` +stdin {"instructions": "...", "turns": [{"prompt": "...", "response": "..."}], + "temperature": 0.7, "maximumResponseTokens": 256, + "sampling": {"mode": "greedy" | "top" | "nucleus", "k": 40, "p": 0.9, "seed": 7}, + "prompt": "..."} +stdout {"event": "delta", "text": "..."} + {"event": "finish", "reason": "stop", "dropped": 2, "inputTokens": 15, "outputTokens": 75} + {"event": "error", "code": "guardrail_violation", "message": "..."} +``` + +Apple streams each snapshot of the reply whole; the bridge writes only +what is new in it. `dropped` counts the oldest turns it left out to fit +the context. `apple-fm-gen --probe` reads nothing and writes +`{"event": "probe", "available": true}`, or `false` with a `reason`: +`apple_intelligence_not_enabled`, `model_not_ready` or +`device_not_eligible`. + +```bash +echo '{"prompt": "Say hello in French."}' | target/release/apple-fm-gen +``` + +## Run + +Needs an Apple silicon Mac on macOS 26 or later, with Apple +Intelligence on, and the Command Line Tools' `swiftc` (no Xcode). + +```bash +hum hive apple-fm install +``` + +`install` builds the nurse (`cargo build --release --locked`) and the +bridge beside it (`swiftc`, into `target/release/apple-fm-gen`), and +keeps the Orchfile's `apple-fm` unit running under orchd, in the +`hum-apple-fm` namespace. A setting rides in the environment of the +install: + +```bash +APPLE_FM_MODEL=apple-on-device hum hive apple-fm install +``` + +The nurse runs from its directory too, once the hive is installed on +the humd it lands on (it enters its own bee when it starts): + +```bash +cd hives/apple-fm && ./build && target/release/apple-fm-nurse +``` + +## Test + +```bash +cd hives/apple-fm && cargo test +``` + +The nurse's tests run it as orchd would, against a humd in a root of its +own, a forager that prompts through it, and a stand-in bridge: a prompt +unfurls and wilts back with its nectar, the sigil's next prompt grows on +from what its cell grew, a cancel kills the bridge, a nurse waits +unnestled until the model is ready and then grows, one on a Mac that +cannot run the model exits 0, and one whose bridge cannot run fails. +Its cells' tests cover the turns a cell keeps, a graft that overrides +them and the turns the bridge dropped, a still, a stop, a season that +ran out, a bridge that lingers after it finished or falls silent but +lives on, and each way a bridge can fail; the unit tests cover the request a prompt becomes, its sampling, +what is refused, what the bridge says, and how `frost` holds back what +may be a stop. + +Against Apple's own model, on a Mac that can run it: + +```bash +./build && cargo test --test live -- --ignored +``` + +It holds a conversation through humd, streams a count with each unfurl +new, stops at a stop, wilts `max_tokens` at its season, grows a +conversation longer than the model's context from its newest turns, and +cancels. + +## Use + +Any forager's client, with the model `apple-3b-on-device`. Through +[`ollama-server`](../ollama-server): + +```bash +curl http://localhost:14624/api/chat \ + -H "Content-Type: application/json" \ + -d '{"model": "apple-3b-on-device", "messages": [{"role": "user", "content": "ping"}]}' +``` + +## What is not yet + +- **No tools.** FoundationModels can call tools, but the bridge offers + the model none, so a bloom's tendrils are not reached. +- **No trellis.** FoundationModels can hold a reply to a schema, but the + bridge grows free text only. +- **Not in the mac desktop bundle.** The hive installs on its own; the + desktop app does not yet carry it. + +## See also + +- [`ollama-server`](../ollama-server), [`dwarfstar`](../dwarfstar): sibling nurses for local models. +- [`hum-nest`](../../hum-nest): the nest the nurse runs on. +- [hives](../README.md): how a hive is installed and how its bees enter. +- [Apple's Foundation Models framework](https://developer.apple.com/documentation/foundationmodels). diff --git a/hives/apple-fm/bridge/gen.swift b/hives/apple-fm/bridge/gen.swift new file mode 100644 index 0000000..b5d1aa4 --- /dev/null +++ b/hives/apple-fm/bridge/gen.swift @@ -0,0 +1,232 @@ +// apple-fm's bridge, and the only code in hum that speaks to Apple. +// +// One run grows one bloom through Apple's on-device foundation model +// (SystemLanguageModel.default, through FoundationModels). It reads one +// JSON line on stdin: +// +// {"instructions": "...", "turns": [{"prompt": "...", "response": "..."}], +// "prompt": "...", "temperature": 0.7, "maximumResponseTokens": 256, +// "sampling": {"mode": "greedy" | "top" | "nucleus", "k": 40, "p": 0.9, "seed": 7}} +// +// and writes NDJSON on stdout, each line as soon as it is known: +// +// {"event": "delta", "text": "..."} the words new since the last line +// {"event": "finish", "reason": "stop" | "max_tokens", "dropped": n, +// "inputTokens": n, "outputTokens": n} (reason and counts on macOS 26.4 and later) +// {"event": "error", "code": "...", "message": "..."} +// +// The model's context is small (4096 tokens), so the oldest turns that do not +// fit are dropped and counted in the finish's `dropped`: the newest turns are +// kept that leave room for the reply, its `maximumResponseTokens` or 1024 +// tokens, and the reply is held to the room that is left, so it ends +// `max_tokens` rather than overflowing. Where Apple cannot +// count tokens, a transcript Apple refuses as too long is retried with the +// older half of its turns dropped. +// +// `gen --probe` reads nothing and writes one line, +// {"event": "probe", "available": true|false, "reason": "..."}. +// +// It exits 0 after a finish or a probe, 1 after an error, 2 on a request it +// cannot read. + +import Foundation +import FoundationModels + +struct Turn: Decodable { + let prompt: String + let response: String +} + +struct Sampling: Decodable { + let mode: String + let k: Int? + let p: Double? + let seed: UInt64? +} + +struct Request: Decodable { + let instructions: String? + let turns: [Turn]? + let prompt: String + let temperature: Double? + let maximumResponseTokens: Int? + let sampling: Sampling? +} + +struct Event: Encodable { + let event: String + var text: String? = nil + var available: Bool? = nil + var reason: String? = nil + var code: String? = nil + var message: String? = nil + var inputTokens: Int? = nil + var outputTokens: Int? = nil + var dropped: Int? = nil +} + +@main +struct Gen { + static func say(_ event: Event) { + let encoder = JSONEncoder() + encoder.outputFormatting = [.withoutEscapingSlashes] + guard var line = try? encoder.encode(event) else { return } + line.append(0x0A) + FileHandle.standardOutput.write(line) + } + + static func fail(_ code: String, _ message: String, exit status: Int32 = 1) -> Never { + say(Event(event: "error", code: code, message: message)) + exit(status) + } + + static func unavailable() -> String? { + switch SystemLanguageModel.default.availability { + case .available: + return nil + case .unavailable(.deviceNotEligible): + return "device_not_eligible" + case .unavailable(.appleIntelligenceNotEnabled): + return "apple_intelligence_not_enabled" + case .unavailable(.modelNotReady): + return "model_not_ready" + case .unavailable(let other): + return String(describing: other) + } + } + + static func sampling(_ s: Sampling?) -> GenerationOptions.SamplingMode? { + guard let s else { return nil } + switch s.mode { + case "greedy": return .greedy + case "top": return .random(top: s.k ?? 50, seed: s.seed) + case "nucleus": return .random(probabilityThreshold: s.p ?? 1.0, seed: s.seed) + default: return nil + } + } + + static func code(_ error: LanguageModelSession.GenerationError) -> String { + switch error { + case .exceededContextWindowSize: return "exceeded_context_window" + case .assetsUnavailable: return "assets_unavailable" + case .guardrailViolation: return "guardrail_violation" + case .unsupportedGuide: return "unsupported_guide" + case .unsupportedLanguageOrLocale: return "unsupported_language" + case .decodingFailure: return "decoding_failure" + case .rateLimited: return "rate_limited" + case .concurrentRequests: return "concurrent_requests" + case .refusal: return "refusal" + @unknown default: return "generation_failed" + } + } + + static func text(_ s: String) -> [Transcript.Segment] { + [.text(Transcript.TextSegment(content: s))] + } + + static func exchange(_ turn: Turn) -> [Transcript.Entry] { + [ + .prompt(Transcript.Prompt(segments: text(turn.prompt))), + .response(Transcript.Response(assetIDs: [], segments: text(turn.response))), + ] + } + + static let reply = 1024 + + @available(macOS 26.4, *) + static func fit(_ turns: [Turn], around fixed: [Transcript.Entry], reserving: Int?) async -> (kept: [Turn], cap: Int)? { + let model = SystemLanguageModel.default + let context = model.contextSize + let reserve = min(reserving ?? reply, context / 2) + guard var used = try? await model.tokenCount(for: fixed) else { return nil } + var kept = 0 + for turn in turns.reversed() { + guard let counted = try? await model.tokenCount(for: exchange(turn)) else { return nil } + let cost = max(0, counted - 1) + if used + cost + reserve > context { break } + used += cost + kept += 1 + } + return (Array(turns.suffix(kept)), max(1, context - used - 16)) + } + + static func main() async { + if CommandLine.arguments.dropFirst().contains("--probe") { + let why = unavailable() + say(Event(event: "probe", available: why == nil, reason: why)) + return + } + guard let line = readLine(strippingNewline: true), + let request = try? JSONDecoder().decode(Request.self, from: Data(line.utf8)) + else { + fail("bad_request", "the bridge reads one JSON request on stdin", exit: 2) + } + if let why = unavailable() { + fail("unavailable", why) + } + + var head: [Transcript.Entry] = [] + if let instructions = request.instructions, !instructions.isEmpty { + head.append(.instructions(Transcript.Instructions(segments: text(instructions), toolDefinitions: []))) + } + let asked: Transcript.Entry = .prompt(Transcript.Prompt(segments: text(request.prompt))) + var turns = request.turns ?? [] + var dropped = 0 + var cap = request.maximumResponseTokens + if #available(macOS 26.4, *), let fitted = await fit(turns, around: head + [asked], reserving: request.maximumResponseTokens) { + dropped = turns.count - fitted.kept.count + turns = fitted.kept + cap = min(cap ?? fitted.cap, fitted.cap) + } + let options = GenerationOptions( + sampling: sampling(request.sampling), + temperature: request.temperature, + maximumResponseTokens: cap + ) + + var said = "" + var sent = 0 + while true { + let session = LanguageModelSession(model: .default, transcript: Transcript(entries: head + turns.flatMap(exchange))) + do { + for try await snapshot in session.streamResponse(to: request.prompt, options: options) { + said = snapshot.content + let scalars = said.unicodeScalars + let count = scalars.count + if count > sent { + var delta = String.UnicodeScalarView() + delta.append(contentsOf: scalars.dropFirst(sent)) + say(Event(event: "delta", text: String(delta))) + sent = count + } + } + break + } catch LanguageModelSession.GenerationError.exceededContextWindowSize where sent == 0 && !turns.isEmpty { + let older = (turns.count + 1) / 2 + turns.removeFirst(older) + dropped += older + } catch let error as LanguageModelSession.GenerationError { + fail(code(error), error.localizedDescription) + } catch { + fail("generation_failed", String(describing: error)) + } + } + + var finish = Event(event: "finish") + if dropped > 0 { + finish.dropped = dropped + } + if #available(macOS 26.4, *) { + let model = SystemLanguageModel.default + finish.inputTokens = try? await model.tokenCount(for: head + turns.flatMap(exchange) + [asked]) + if let counted = try? await model.tokenCount(for: said), + let framing = try? await model.tokenCount(for: "") { + finish.outputTokens = max(0, counted - framing) + } + if let out = finish.outputTokens, let cap { + finish.reason = out >= cap ? "max_tokens" : "stop" + } + } + say(finish) + } +} diff --git a/hives/apple-fm/build b/hives/apple-fm/build new file mode 100755 index 0000000..02014cc --- /dev/null +++ b/hives/apple-fm/build @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Build this hive's bee: the nurse, and beside it apple-fm-gen, the Swift +# bridge to Apple's on-device model. +# +# The hive is a kind; this makes its material. The hive's own install +# runs this when a humd installs the hive, so the hive needs no knowledge +# outside its own directory, and nothing is installed into the system's +# PATH: the bee is built here and runs here. +# +# FoundationModels ships with macOS 26, so the bridge builds only there, +# with the Command Line Tools' swiftc; no Xcode is needed. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$here" + +if [ "$(uname -s)" != Darwin ] || [ "$(uname -m)" != arm64 ]; then + echo "apple-fm: Apple's on-device model runs only on Apple silicon Macs with macOS 26 or later" >&2 + exit 1 +fi + +cargo build --quiet --release --locked +swiftc -parse-as-library -O -target arm64-apple-macos26.0 bridge/gen.swift -o target/release/apple-fm-gen diff --git a/hives/apple-fm/install b/hives/apple-fm/install new file mode 100755 index 0000000..5805207 --- /dev/null +++ b/hives/apple-fm/install @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Install this hive on a humd: build its bee and keep its unit running +# under orchd. The nurse enters itself under the hive when it starts, and +# waits unnestled while Apple's model is not ready on this Mac. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +hive="${HUM_HIVE:-$(basename "$here")}" +state="${XDG_STATE_HOME:-$HOME/.local/state}/hum/hives/$hive" +orchd="$(command -v orchd || echo "$HOME/.local/bin/orchd")" +mkdir -p "$state" + +"$here/build" + +args=(--arg "HUM_SRC=$here") +while read -r name; do + if [ -n "${!name:-}" ]; then args+=(--arg "$name=${!name}"); fi +done < <(sed -n 's/^ARG \([A-Za-z_][A-Za-z0-9_]*\)=.*/\1/p' "$here/Orchfile" | grep -vx -e HUM_SRC -e HUM_HIVE) + +units=() +while read -r unit; do units+=("$unit"); done < <(sed -n 's/^SERVICE \([A-Za-z0-9_.-]*\)$/\1/p' "$here/Orchfile" | grep -vx hive-install) + +run() { "$orchd" --project-dir "$here" --namespace "hum-$hive" --user --state-dir "$state/orch" "${args[@]}" "$@"; } +run fell >/dev/null 2>&1 || true +run grow "${units[@]}" diff --git a/hives/apple-fm/src/bridge.rs b/hives/apple-fm/src/bridge.rs new file mode 100644 index 0000000..a634295 --- /dev/null +++ b/hives/apple-fm/src/bridge.rs @@ -0,0 +1,356 @@ +//! The bridge's wire: what one run of `apple-fm-gen` is asked, and what it +//! says back. +//! +//! A prompt becomes one JSON line on the bridge's stdin. The prompt's system +//! prompt becomes Apple's instructions, the conversation before it becomes +//! turns, each a prompt and the response it grew, and how the bloom grows +//! becomes Apple's generation options: `temperature`, `season` as the most +//! tokens it may grow, and `prune` and `seed` as how it samples, greedily +//! at a temperature of 0. The bridge answers in NDJSON, each line a +//! [`Said`]; its finish says how many of the oldest turns it dropped to fit +//! the model's context. +//! +//! Apple's model grows words from words. A prompt that carries pollen or +//! asks for a `trellis` is refused rather than grown as if it had not. + +use std::path::Path; +use std::time::Duration; + +use anyhow::{bail, Context, Result}; +use serde::{Deserialize, Serialize}; +use thrum_core::{Bloom, Petal, PetalKind, PromptBody}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Turn { + pub prompt: String, + pub response: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(tag = "mode", rename_all = "lowercase")] +pub enum Sampling { + Greedy, + Top { k: u64, seed: Option }, + Nucleus { p: f64, seed: Option }, +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Ask { + #[serde(skip_serializing_if = "Option::is_none")] + pub instructions: Option, + pub turns: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub temperature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub maximum_response_tokens: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sampling: Option, + pub prompt: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(tag = "event", rename_all = "lowercase")] +pub enum Said { + Delta { + text: String, + }, + Finish { + reason: Option, + dropped: Option, + #[serde(rename = "inputTokens")] + input_tokens: Option, + #[serde(rename = "outputTokens")] + output_tokens: Option, + }, + Error { + code: String, + message: String, + }, + Probe { + available: bool, + reason: Option, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Refusal { + pub code: &'static str, + pub message: String, +} + +fn is(petal: &Petal, kind: PetalKind) -> bool { + petal.kind == kind.as_str() +} + +pub fn words(petals: &[Petal]) -> String { + petals + .iter() + .filter(|p| is(p, PetalKind::Text)) + .filter_map(|p| p.text.as_deref()) + .collect::>() + .join("\n") +} + +fn grown(petals: &[Petal]) -> String { + petals + .iter() + .filter(|p| is(p, PetalKind::Text)) + .filter_map(|p| p.text.as_deref()) + .collect() +} + +pub fn turns(graft: &[Bloom]) -> Vec { + graft + .iter() + .map(|bloom| Turn { + prompt: words(bloom.content.as_deref().unwrap_or(&[])), + response: grown(bloom.grew.as_deref().unwrap_or(&[])), + }) + .filter(|turn| !turn.prompt.is_empty() || !turn.response.is_empty()) + .collect() +} + +pub fn said_by(prompt: &PromptBody) -> String { + let content = prompt.content.as_deref().unwrap_or(&[]); + let said = words(content); + if !said.is_empty() { + return said; + } + prompt.text.clone().unwrap_or_default() +} + +pub fn sampling(prompt: &PromptBody) -> Option { + if prompt.temperature == Some(0.0) { + return Some(Sampling::Greedy); + } + let seed = prompt.seed.map(|s| s as u64); + let prune = prompt.prune.as_ref(); + if let Some(k) = prune.and_then(|p| p.k) { + return Some(Sampling::Top { k, seed }); + } + if let Some(p) = prune.and_then(|p| p.p) { + return Some(Sampling::Nucleus { p, seed }); + } + seed.map(|seed| Sampling::Nucleus { p: 1.0, seed: Some(seed) }) +} + +pub fn ask(prompt: &PromptBody, before: &[Turn]) -> Result { + let content = prompt.content.as_deref().unwrap_or(&[]); + if content.iter().any(|p| is(p, PetalKind::Pollen)) { + return Err(Refusal { + code: "apple_fm_words_only", + message: "Apple's on-device model grows from words alone, and this prompt carries pollen".into(), + }); + } + if prompt.trellis.is_some() { + return Err(Refusal { + code: "apple_fm_no_trellis", + message: "apple-fm grows free text, and cannot hold a bloom to a trellis".into(), + }); + } + let said = said_by(prompt); + if said.is_empty() { + return Err(Refusal { code: "apple_fm_no_words", message: "apple-fm grows a bloom only from new words".into() }); + } + let sampling = sampling(prompt); + Ok(Ask { + instructions: prompt.system_prompt.clone().filter(|s| !s.is_empty()), + turns: before.to_vec(), + temperature: if sampling == Some(Sampling::Greedy) { None } else { prompt.temperature }, + maximum_response_tokens: prompt.season, + sampling, + prompt: said, + }) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Availability { + Available, + Dormant(String), + Never(String), +} + +pub async fn probe(bridge: &Path) -> Result { + let run = tokio::process::Command::new(bridge).arg("--probe").kill_on_drop(true).output(); + let out = tokio::time::timeout(Duration::from_secs(30), run) + .await + .with_context(|| format!("{} --probe did not answer in 30s", bridge.display()))? + .with_context(|| format!("run {} --probe", bridge.display()))?; + let text = String::from_utf8_lossy(&out.stdout); + let heard = text.lines().rev().find_map(|line| serde_json::from_str::(line).ok()); + match heard { + Some(Said::Probe { available: true, .. }) => Ok(Availability::Available), + Some(Said::Probe { reason, .. }) => { + let reason = reason.unwrap_or_else(|| "unavailable".into()); + Ok(if reason == "device_not_eligible" { Availability::Never(reason) } else { Availability::Dormant(reason) }) + } + _ => bail!( + "{} --probe said no probe ({}): {}{}", + bridge.display(), + out.status, + text.trim(), + String::from_utf8_lossy(&out.stderr).trim() + ), + } +} + +pub struct Frost { + stops: Vec, + held: String, +} + +impl Frost { + pub fn new(stops: Option<&[String]>) -> Frost { + let stops = stops.unwrap_or(&[]).iter().filter(|s| !s.is_empty()).cloned().collect(); + Frost { stops, held: String::new() } + } + + pub fn pass(&mut self, text: &str) -> (String, bool) { + if self.stops.is_empty() { + return (text.to_string(), false); + } + self.held.push_str(text); + if let Some(at) = self.stops.iter().filter_map(|s| self.held.find(s.as_str())).min() { + let said = self.held[..at].to_string(); + self.held.clear(); + return (said, true); + } + let longest = self.stops.iter().map(String::len).max().unwrap_or(0); + let from = self.held.len().saturating_sub(longest.saturating_sub(1)); + let cut = self + .held + .char_indices() + .map(|(at, _)| at) + .filter(|at| *at >= from) + .find(|at| self.stops.iter().any(|s| s.starts_with(&self.held[*at..]))) + .unwrap_or(self.held.len()); + let said: String = self.held.drain(..cut).collect(); + (said, false) + } + + pub fn thaw(&mut self) -> String { + std::mem::take(&mut self.held) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::{json, Value}; + + fn prompt(v: Value) -> PromptBody { + serde_json::from_value(v).unwrap() + } + + #[test] + fn a_graft_becomes_the_turns_before_the_prompt() { + let p = prompt(json!({ + "chi": "prompt", + "systemPrompt": "be brief", + "content": [{"kind": "text", "text": "and now?"}], + "graft": [ + {"content": [{"kind": "text", "text": "hi"}], + "grew": [{"kind": "sepal", "text": "hm"}, {"kind": "text", "text": "hello "}, {"kind": "text", "text": "there"}]}, + {"content": [{"kind": "text", "text": "name a fruit"}], "grew": [{"kind": "text", "text": "apple"}]}, + {"content": [], "grew": []}, + ], + })); + let before = turns(p.graft.as_deref().unwrap()); + assert_eq!( + before, + [ + Turn { prompt: "hi".into(), response: "hello there".into() }, + Turn { prompt: "name a fruit".into(), response: "apple".into() }, + ], + "the words of each bloom, its thinking aside, and an empty bloom dropped" + ); + let ask = ask(&p, &before).unwrap(); + assert_eq!(ask.instructions.as_deref(), Some("be brief")); + assert_eq!(ask.prompt, "and now?"); + assert_eq!(ask.turns.len(), 2); + } + + #[test] + fn the_request_is_one_line_with_the_prompt_last() { + let p = prompt(json!({"text": "say \"hi\"\non two lines", "season": 32, "temperature": 0.4})); + let line = serde_json::to_string(&ask(&p, &[Turn { prompt: "a".into(), response: "b".into() }]).unwrap()).unwrap(); + assert!(!line.contains('\n')); + assert_eq!( + serde_json::from_str::(&line).unwrap(), + json!({"turns": [{"prompt": "a", "response": "b"}], "temperature": 0.4, "maximumResponseTokens": 32, "prompt": "say \"hi\"\non two lines"}) + ); + assert!(line.ends_with(r#""prompt":"say \"hi\"\non two lines"}"#), "{line}"); + } + + #[test] + fn how_a_bloom_grows_becomes_how_apple_samples() { + let s = |v: Value| sampling(&prompt(v)); + assert_eq!(s(json!({"temperature": 0.0, "prune": {"k": 5}})), Some(Sampling::Greedy)); + assert_eq!(s(json!({"prune": {"k": 40, "p": 0.9}, "seed": 7})), Some(Sampling::Top { k: 40, seed: Some(7) })); + assert_eq!(s(json!({"prune": {"p": 0.9}})), Some(Sampling::Nucleus { p: 0.9, seed: None })); + assert_eq!(s(json!({"seed": 3})), Some(Sampling::Nucleus { p: 1.0, seed: Some(3) })); + assert_eq!(s(json!({"temperature": 0.7})), None); + + let greedy = ask(&prompt(json!({"text": "x", "temperature": 0.0})), &[]).unwrap(); + assert_eq!(greedy.temperature, None, "greedy needs no temperature"); + assert_eq!(serde_json::to_value(&greedy.sampling).unwrap(), json!({"mode": "greedy"})); + let top = serde_json::to_value(Sampling::Top { k: 40, seed: None }).unwrap(); + assert_eq!(top, json!({"mode": "top", "k": 40, "seed": null})); + } + + #[test] + fn pollen_a_trellis_and_no_words_are_refused() { + let pollen = prompt(json!({"content": [ + {"kind": "text", "text": "what is this?"}, + {"kind": "pollen", "grain": {"kind": "image", "mediaType": "image/png", "data": "aGk="}}, + ]})); + assert_eq!(ask(&pollen, &[]).unwrap_err().code, "apple_fm_words_only"); + let trellis = prompt(json!({"text": "json please", "trellis": "json"})); + assert_eq!(ask(&trellis, &[]).unwrap_err().code, "apple_fm_no_trellis"); + assert_eq!(ask(&prompt(json!({"content": []})), &[]).unwrap_err().code, "apple_fm_no_words"); + } + + #[test] + fn what_the_bridge_says_is_read() { + let read = |s: &str| serde_json::from_str::(s).unwrap(); + assert_eq!(read(r#"{"text":"hi","event":"delta"}"#), Said::Delta { text: "hi".into() }); + assert_eq!( + read(r#"{"event":"finish","reason":"max_tokens","dropped":2,"inputTokens":3,"outputTokens":2}"#), + Said::Finish { reason: Some("max_tokens".into()), dropped: Some(2), input_tokens: Some(3), output_tokens: Some(2) } + ); + assert_eq!( + read(r#"{"event":"finish"}"#), + Said::Finish { reason: None, dropped: None, input_tokens: None, output_tokens: None } + ); + assert_eq!( + read(r#"{"event":"error","code":"refusal","message":"no"}"#), + Said::Error { code: "refusal".into(), message: "no".into() } + ); + assert_eq!( + read(r#"{"event":"probe","available":false,"reason":"model_not_ready"}"#), + Said::Probe { available: false, reason: Some("model_not_ready".into()) } + ); + } + + #[test] + fn frost_holds_back_what_may_be_a_stop_and_ends_at_one() { + let stops = ["THREE".to_string(), "\n\n".to_string()]; + let mut frost = Frost::new(Some(&stops)); + assert_eq!(frost.pass("one two TH"), ("one two ".into(), false), "TH may begin THREE"); + assert_eq!(frost.pass("REE four"), ("".into(), true), "a stop across two deltas is met"); + + let mut frost = Frost::new(Some(&stops)); + assert_eq!(frost.pass("TH"), ("".into(), false)); + assert_eq!(frost.pass("ROW"), ("THROW".into(), false), "what turned out not to be a stop is said"); + assert_eq!(frost.pass("n\n"), ("n".into(), false)); + assert_eq!(frost.pass("x"), ("\nx".into(), false)); + assert_eq!(frost.pass("é\n"), ("é".into(), false), "held back on a char boundary"); + assert_eq!(frost.thaw(), "\n", "and what is held is said when the bloom finishes"); + + let mut none = Frost::new(None); + assert_eq!(none.pass("THREE"), ("THREE".into(), false)); + let mut empty = Frost::new(Some(&["".to_string()])); + assert_eq!(empty.pass("x"), ("x".into(), false), "an empty stop stops nothing"); + } +} diff --git a/hives/apple-fm/src/main.rs b/hives/apple-fm/src/main.rs new file mode 100644 index 0000000..f1c1760 --- /dev/null +++ b/hives/apple-fm/src/main.rs @@ -0,0 +1,86 @@ +//! apple-fm-nurse — Apple's on-device foundation model, as a nurse. +//! +//! It grows the model it advertises through `apple-fm-gen`, the Swift bridge +//! to FoundationModels beside it, and needs no network and no key. Before it +//! nestles it asks the bridge whether the model can run here. While Apple +//! Intelligence is off, or its model is still downloading, it waits without +//! nestling and asks again; on a Mac that cannot run the model at all it +//! says so and exits 0, so its unit stays down. A bridge that cannot be run +//! at all exits non-zero. +//! +//! Environment, an empty value being no value: +//! APPLE_FM_MODEL — the model id it advertises (default: apple-3b-on-device) +//! APPLE_FM_LABEL — the label its bee is entered as (default: main) +//! APPLE_FM_BRIDGE — the bridge (default: apple-fm-gen beside this binary) +//! APPLE_FM_PROBE_MS — how often a waiting nurse asks again (default: 60000) + +mod bridge; +mod nurse; + +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::Result; +use hum_nest::{serve_nurse, NurseAdvert}; +use tracing::{info, warn}; + +use crate::bridge::Availability; +use crate::nurse::AppleFm; + +const HIVE: &str = "apple-fm"; + +fn var(name: &str) -> Option { + std::env::var(name).ok().filter(|v| !v.trim().is_empty()) +} + +fn beside_me() -> PathBuf { + std::env::current_exe() + .ok() + .and_then(|exe| exe.parent().map(|dir| dir.join("apple-fm-gen"))) + .unwrap_or_else(|| PathBuf::from("apple-fm-gen")) +} + +#[tokio::main] +async fn main() -> Result<()> { + hum_paths::init(); + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .init(); + + let model = var("APPLE_FM_MODEL").unwrap_or_else(|| "apple-3b-on-device".into()); + let label = var("APPLE_FM_LABEL").unwrap_or_else(|| "main".into()); + let bridge = var("APPLE_FM_BRIDGE").map(PathBuf::from).unwrap_or_else(beside_me); + let every = Duration::from_millis(var("APPLE_FM_PROBE_MS").and_then(|ms| ms.trim().parse().ok()).unwrap_or(60_000)); + + let mut waiting: Option = None; + loop { + match bridge::probe(&bridge).await? { + Availability::Available => break, + Availability::Never(reason) => { + warn!(%reason, bridge = %bridge.display(), "apple-fm.never: this Mac cannot run Apple's on-device model, so this unit stays down"); + return Ok(()); + } + Availability::Dormant(reason) => { + if waiting.as_deref() != Some(reason.as_str()) { + warn!(%reason, every_ms = every.as_millis() as u64, "apple-fm.dormant: Apple's on-device model is not ready, asking again"); + } + waiting = Some(reason); + tokio::time::sleep(every).await; + } + } + } + info!(bridge = %bridge.display(), %model, "apple-fm.available"); + + let advert = NurseAdvert { + hive: HIVE.to_string(), + label, + version: env!("CARGO_PKG_VERSION").to_string(), + models: vec![model], + source: Some("https://github.com/adiled/hum/tree/main/hives/apple-fm".to_string()), + }; + serve_nurse(Arc::new(AppleFm { bridge }), advert).await +} diff --git a/hives/apple-fm/src/nurse.rs b/hives/apple-fm/src/nurse.rs new file mode 100644 index 0000000..138ef5b --- /dev/null +++ b/hives/apple-fm/src/nurse.rs @@ -0,0 +1,543 @@ +//! The nurse that grows blooms through Apple's on-device foundation model. +//! +//! Each prompt runs the bridge once: it is asked the prompt as one JSON line +//! and streams back what the model grows, and the words it grows unfurl as a +//! text petal. The bloom wilts `stop` when the bridge finishes, with +//! `max_tokens` as the reason when the prompt's `season` ran out, and its +//! counts as nectar when Apple gives them. A bloom that meets one of its +//! `frost` stops there: what came before the stop unfurls, and the bridge is +//! killed. The bridge's own errors, and a bridge that ends without a word, +//! become `error`s. +//! +//! A cell lives for its sigil and remembers what it grew. A prompt that +//! carries a graft is grown from that graft; one that carries none, as a +//! forager that sends only the new words does, is grown from the turns the +//! cell has grown so far. So a sigil holds its conversation either way. The +//! oldest turns the bridge dropped to fit the model's context, the cell +//! forgets too. +//! +//! A still kills the bridge mid-bloom and wilts the bloom `cancel`, and a +//! prompt still waiting is told it was not grown. + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; +use std::process::Stdio; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::Result; +use async_trait::async_trait; +use serde_json::{json, Value}; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; +use tokio::process::Command; +use tokio::sync::{mpsc, oneshot, Mutex}; +use tokio_util::sync::CancellationToken; +use tracing::{info, warn}; + +use hum_nest::{Cell, Egg, Nurse, Propensity}; +use thrum_core::{PetalKind, PromptBody}; + +use crate::bridge::{self, Frost, Said, Turn}; + +pub struct AppleFm { + pub bridge: PathBuf, +} + +#[async_trait] +impl Nurse for AppleFm { + fn ephemeral(&self) -> bool { + false + } + + fn propensity(&self) -> Propensity { + Propensity::StatefulSession + } + + async fn raise(&self, egg: Egg) -> Result { + info!(sid = %egg.sid, model = %egg.model_id, "apple-fm.raise"); + let bridge = self.bridge.clone(); + let (tx_feed, mut rx_feed) = mpsc::channel::(64); + let (tx_mmm, rx_mmm) = mpsc::channel::(256); + let (tx_exit, rx_exit) = oneshot::channel::(); + let silence = CancellationToken::new(); + let stilled = silence.clone(); + + tokio::spawn(async move { + let mut grew: Vec = Vec::new(); + loop { + let tone = tokio::select! { + biased; + _ = stilled.cancelled() => break, + tone = rx_feed.recv() => match tone { + Some(tone) => tone, + None => break, + }, + }; + if tone.get("chi").and_then(Value::as_str) != Some("prompt") { + continue; + } + let prompt: PromptBody = serde_json::from_value(tone).unwrap_or_default(); + let mut before = match prompt.graft.as_deref() { + Some(graft) if !graft.is_empty() => bridge::turns(graft), + _ => grew.clone(), + }; + if let Some((turn, dropped)) = grow(&bridge, &prompt, &before, &tx_mmm, &stilled).await { + before.drain(..dropped.min(before.len())); + before.push(turn); + grew = before; + } + } + rx_feed.close(); + while let Ok(tone) = rx_feed.try_recv() { + if tone.get("chi").and_then(Value::as_str) == Some("prompt") { + let _ = tx_mmm.send(fail("apple_fm_cell_stilled", "the cell was stilled before it grew this prompt".into())).await; + } + } + let _ = tx_exit.send(0); + }); + + Ok(Cell { + mark: None, + feed: tx_feed, + mmm: Arc::new(Mutex::new(rx_mmm)), + emerged: rx_exit, + ephemeral: false, + silence, + }) + } +} + +fn fail(code: &str, message: String) -> Value { + json!({ "chi": "error", "code": code, "message": message }) +} + +struct Unfurler { + tx: mpsc::Sender, + petals: BTreeMap<&'static str, u64>, +} + +impl Unfurler { + async fn grow(&mut self, kind: PetalKind, text: &str) { + if text.is_empty() { + return; + } + let next = self.petals.len() as u64; + let petal = *self.petals.entry(kind.as_str()).or_insert(next); + let _ = self.tx.send(json!({ "chi": "unfurl", "petal": petal, "kind": kind.as_str(), "text": text })).await; + } + + async fn close(&mut self) { + for (kind, petal) in std::mem::take(&mut self.petals) { + let _ = self.tx.send(json!({ "chi": "unfurl", "petal": petal, "kind": kind, "closed": true })).await; + } + } +} + +fn tail(text: &str) -> &str { + let text = text.trim(); + let from = text.len().saturating_sub(2000); + let from = (from..=text.len()).find(|at| text.is_char_boundary(*at)).unwrap_or(text.len()); + &text[from..] +} + +async fn grow( + bridge: &Path, + prompt: &PromptBody, + before: &[Turn], + tx: &mpsc::Sender, + stilled: &CancellationToken, +) -> Option<(Turn, usize)> { + let ask = match bridge::ask(prompt, before) { + Ok(ask) => ask, + Err(refusal) => { + let _ = tx.send(fail(refusal.code, refusal.message)).await; + return None; + } + }; + let mut child = match Command::new(bridge) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .kill_on_drop(true) + .spawn() + { + Ok(child) => child, + Err(e) => { + let _ = tx.send(fail("apple_fm_bridge_spawn", format!("could not run the bridge at {}: {e}", bridge.display()))).await; + return None; + } + }; + if let Some(mut stdin) = child.stdin.take() { + let mut line = serde_json::to_vec(&ask).unwrap_or_default(); + line.push(b'\n'); + if let Err(e) = stdin.write_all(&line).await { + warn!(err = %e, "apple-fm.bridge.stdin"); + } + } + let complaint = child.stderr.take().map(|mut stderr| { + tokio::spawn(async move { + let mut text = String::new(); + let _ = stderr.read_to_string(&mut text).await; + text + }) + }); + let Some(stdout) = child.stdout.take() else { + let _ = tx.send(fail("apple_fm_bridge_spawn", "the bridge has no stdout".into())).await; + return None; + }; + let mut lines = BufReader::new(stdout).lines(); + let mut unfurler = Unfurler { tx: tx.clone(), petals: BTreeMap::new() }; + let mut frost = Frost::new(prompt.frost.as_deref()); + let mut said = String::new(); + let turn = |said: String| Turn { prompt: ask.prompt.clone(), response: said }; + + loop { + let line = tokio::select! { + biased; + _ = stilled.cancelled() => { + let _ = child.kill().await; + unfurler.close().await; + let _ = tx.send(json!({ "chi": "wilt", "reason": "cancel" })).await; + return None; + } + line = lines.next_line() => line, + }; + let line = match line { + Ok(Some(line)) => line, + Ok(None) => break, + Err(e) => { + warn!(err = %e, "apple-fm.bridge.stdout"); + break; + } + }; + let heard = match serde_json::from_str::(&line) { + Ok(heard) => heard, + Err(e) => { + warn!(err = %e, line = %line.chars().take(200).collect::(), "apple-fm.bridge.unread"); + continue; + } + }; + match heard { + Said::Delta { text } => { + let (free, met) = frost.pass(&text); + said.push_str(&free); + unfurler.grow(PetalKind::Text, &free).await; + if met { + let _ = child.kill().await; + unfurler.close().await; + let _ = tx.send(json!({ "chi": "wilt", "reason": "stop" })).await; + return Some((turn(said), 0)); + } + } + Said::Finish { reason, dropped, input_tokens, output_tokens } => { + let dropped = dropped.unwrap_or(0) as usize; + if dropped > 0 { + info!(dropped, kept = before.len().saturating_sub(dropped), "apple-fm.trimmed"); + } + let thawed = frost.thaw(); + said.push_str(&thawed); + unfurler.grow(PetalKind::Text, &thawed).await; + unfurler.close().await; + let ran_out = match reason.as_deref() { + Some("max_tokens") => true, + Some("stop") => false, + _ => matches!((prompt.season, output_tokens), (Some(season), Some(out)) if out >= season), + }; + let mut wilt = json!({ "chi": "wilt", "reason": if ran_out { "max_tokens" } else { "stop" } }); + if input_tokens.is_some() || output_tokens.is_some() { + wilt["nectar"] = json!({ + "input_tokens": input_tokens.unwrap_or(0), + "output_tokens": output_tokens.unwrap_or(0), + }); + } + let _ = tx.send(wilt).await; + let _ = child.kill().await; + return Some((turn(said), dropped)); + } + Said::Error { code, message } => { + unfurler.close().await; + let _ = tx.send(fail(&format!("apple_fm_{code}"), message)).await; + let _ = child.kill().await; + return None; + } + Said::Probe { .. } => {} + } + } + + let status = match tokio::time::timeout(Duration::from_secs(2), child.wait()).await { + Ok(status) => status.map(|s| s.to_string()).unwrap_or_else(|e| e.to_string()), + Err(_) => { + let _ = child.kill().await; + "it closed its stdout and lived on, so it was killed".to_string() + } + }; + let complaint = match complaint { + Some(task) => tokio::time::timeout(Duration::from_secs(2), task).await.ok().and_then(Result::ok).unwrap_or_default(), + None => String::new(), + }; + unfurler.close().await; + let _ = tx + .send(fail("apple_fm_bridge_died", format!("the bridge ended before it finished ({status}): {}", tail(&complaint)))) + .await; + None +} + +#[cfg(test)] +mod tests { + use super::*; + + const STUB: &str = r#"#!/bin/sh +here="$(cd "$(dirname "$0")" && pwd)" +IFS= read -r line +printf '%s\n' "$line" >> "$here/asked" +echo $$ > "$here/pid" +words=$(printf '%s' "$line" | sed -n 's/.*"prompt":"\([^"]*\)"}$/\1/p') +case "$words" in + boom*) echo '{"event":"error","code":"guardrail_violation","message":"the guardrail held"}'; exit 1 ;; + die*) echo 'the bridge fell over' >&2; exit 3 ;; + hang*) echo '{"event":"delta","text":"hanging"}'; exec sleep 30 ;; + frost*) echo '{"event":"delta","text":"one two TH"}'; echo '{"event":"delta","text":"REE four"}'; exec sleep 30 ;; + long*) echo '{"event":"delta","text":"on and on"}'; echo '{"event":"finish","inputTokens":4,"outputTokens":8}'; exit 0 ;; + capped*) echo '{"event":"delta","text":"cut"}'; echo '{"event":"finish","reason":"max_tokens","outputTokens":3}'; exit 0 ;; + trim*) echo '{"event":"delta","text":"trimmed"}'; echo '{"event":"finish","reason":"stop","dropped":2}'; exit 0 ;; + linger*) echo '{"event":"delta","text":"lingering"}'; echo '{"event":"finish"}'; exec sleep 30 ;; + mute*) echo '{"event":"delta","text":"muted"}'; exec 1>&-; exec sleep 30 ;; +esac +echo '{"event":"delta","text":"heard "}' +printf '{"event":"delta","text":"%s. "}\n' "$words" +echo '{"event":"finish","inputTokens":7,"outputTokens":3}' +"#; + + struct Stub { + dir: tempfile::TempDir, + } + + impl Stub { + fn new() -> Stub { + use std::os::unix::fs::PermissionsExt; + let dir = tempfile::tempdir().unwrap(); + let bridge = dir.path().join("apple-fm-gen"); + std::fs::write(&bridge, STUB).unwrap(); + std::fs::set_permissions(&bridge, std::fs::Permissions::from_mode(0o755)).unwrap(); + Stub { dir } + } + + fn nurse(&self) -> AppleFm { + AppleFm { bridge: self.dir.path().join("apple-fm-gen") } + } + + fn asked(&self) -> Vec { + std::fs::read_to_string(self.dir.path().join("asked")) + .unwrap_or_default() + .lines() + .map(|l| serde_json::from_str(l).unwrap()) + .collect() + } + + fn alive(&self) -> bool { + let pid = std::fs::read_to_string(self.dir.path().join("pid")).unwrap(); + std::process::Command::new("kill").args(["-0", pid.trim()]).stderr(std::process::Stdio::null()).status().unwrap().success() + } + } + + fn fed(words: &str) -> Value { + json!({"chi": "prompt", "sid": "s", "content": [{"kind": "text", "text": words}]}) + } + + async fn raised(stub: &Stub) -> Cell { + let egg = Egg::new(hum_identity::HumId::mint(), "apple-3b-on-device", "/tmp"); + stub.nurse().raise(egg).await.unwrap() + } + + async fn closing(cell: &Cell, blooms: usize) -> Vec { + let mut mmm = cell.mmm.lock().await; + let mut heard = Vec::new(); + while heard.iter().filter(|t: &&Value| t["chi"] == "wilt" || t["chi"] == "error").count() < blooms { + let tone = tokio::time::timeout(Duration::from_secs(10), mmm.recv()).await; + heard.push(tone.expect("the cell answers").expect("the cell lives on")); + } + heard + } + + fn said(tones: &[Value]) -> String { + tones.iter().filter(|t| t["chi"] == "unfurl").filter_map(|t| t["text"].as_str()).collect() + } + + fn ends(tones: &[Value]) -> Vec { + tones.iter().filter(|t| t["chi"] == "wilt" || t["chi"] == "error").cloned().collect() + } + + #[tokio::test] + async fn a_prompt_unfurls_and_wilts_with_its_nectar() { + let stub = Stub::new(); + let cell = raised(&stub).await; + cell.feed.send(fed("hello")).await.unwrap(); + let tones = closing(&cell, 1).await; + assert_eq!(said(&tones), "heard hello. "); + assert_eq!( + tones.iter().filter(|t| t["chi"] == "unfurl").map(|t| (t["petal"].clone(), t["closed"].clone())).collect::>(), + [(json!(0), Value::Null), (json!(0), Value::Null), (json!(0), json!(true))], + "one text petal, closed before the wilt" + ); + assert_eq!(ends(&tones), [json!({"chi": "wilt", "reason": "stop", "nectar": {"input_tokens": 7, "output_tokens": 3}})]); + } + + #[tokio::test] + async fn a_cell_remembers_its_turns_and_a_graft_overrides_them() { + let stub = Stub::new(); + let cell = raised(&stub).await; + let mut first = fed("one"); + first["systemPrompt"] = json!("be brief"); + cell.feed.send(first).await.unwrap(); + cell.feed.send(fed("two")).await.unwrap(); + assert_eq!(said(&closing(&cell, 2).await), "heard one. heard two. "); + let graft = json!([{"content": [{"kind": "text", "text": "elsewhere"}], "grew": [{"kind": "text", "text": "then"}]}]); + let mut grafted = fed("three"); + grafted["graft"] = graft; + cell.feed.send(grafted).await.unwrap(); + cell.feed.send(fed("four")).await.unwrap(); + closing(&cell, 2).await; + + let asked = stub.asked(); + assert_eq!(asked[0], json!({"instructions": "be brief", "turns": [], "prompt": "one"})); + assert_eq!(asked[1]["turns"], json!([{"prompt": "one", "response": "heard one. "}]), "the cell's own turn"); + assert_eq!(asked[2]["turns"], json!([{"prompt": "elsewhere", "response": "then"}]), "the graft, not the cell's turns"); + assert_eq!( + asked[3]["turns"], + json!([{"prompt": "elsewhere", "response": "then"}, {"prompt": "three", "response": "heard three. "}]), + "and the cell grows on from the graft" + ); + } + + #[tokio::test] + async fn a_still_kills_the_bridge_and_closes_what_it_was_fed() { + let stub = Stub::new(); + let cell = raised(&stub).await; + cell.feed.send(fed("hang on")).await.unwrap(); + cell.feed.send(fed("waiting")).await.unwrap(); + { + let mut mmm = cell.mmm.lock().await; + let first = tokio::time::timeout(Duration::from_secs(10), mmm.recv()).await.unwrap().unwrap(); + assert_eq!(first["text"], "hanging"); + } + assert!(stub.alive()); + cell.still(); + let tones = closing(&cell, 2).await; + let ends: Vec = ends(&tones) + .iter() + .map(|t| format!("{} {}", t["chi"].as_str().unwrap(), t["reason"].as_str().or(t["code"].as_str()).unwrap())) + .collect(); + assert_eq!(ends, ["wilt cancel", "error apple_fm_cell_stilled"], "the growing bloom wilts, the waiting one is told"); + assert!(!stub.alive(), "the bridge is killed, not left to grow"); + assert!(cell.mmm.lock().await.recv().await.is_none(), "then the cell ends"); + assert_eq!(cell.emerged.await, Ok(0)); + } + + #[tokio::test] + async fn a_stop_ends_the_bloom_before_it_and_kills_the_bridge() { + let stub = Stub::new(); + let cell = raised(&stub).await; + let mut frosted = fed("frost"); + frosted["frost"] = json!(["THREE"]); + cell.feed.send(frosted).await.unwrap(); + let tones = closing(&cell, 1).await; + assert_eq!(said(&tones), "one two "); + assert_eq!(ends(&tones), [json!({"chi": "wilt", "reason": "stop"})]); + assert!(!stub.alive()); + cell.feed.send(fed("after")).await.unwrap(); + closing(&cell, 1).await; + assert_eq!(stub.asked()[1]["turns"], json!([{"prompt": "frost", "response": "one two "}]), "the turn is what was said"); + } + + #[tokio::test] + async fn a_season_that_ran_out_wilts_max_tokens() { + let stub = Stub::new(); + let cell = raised(&stub).await; + let mut seasoned = fed("long"); + seasoned["season"] = json!(8); + cell.feed.send(seasoned).await.unwrap(); + let tones = closing(&cell, 1).await; + assert_eq!(ends(&tones)[0]["reason"], "max_tokens"); + assert_eq!(stub.asked()[0]["maximumResponseTokens"], 8); + let mut roomy = fed("long"); + roomy["season"] = json!(9); + cell.feed.send(roomy).await.unwrap(); + assert_eq!(ends(&closing(&cell, 1).await)[0]["reason"], "stop"); + } + + #[tokio::test] + async fn the_bridge_s_own_reason_is_the_wilt_s() { + let stub = Stub::new(); + let cell = raised(&stub).await; + cell.feed.send(fed("capped")).await.unwrap(); + assert_eq!(ends(&closing(&cell, 1).await)[0]["reason"], "max_tokens", "a reply the bridge held to the room left ran out"); + } + + #[tokio::test] + async fn the_turns_the_bridge_dropped_the_cell_forgets() { + let stub = Stub::new(); + let cell = raised(&stub).await; + for words in ["one", "two", "three", "trim now", "after"] { + cell.feed.send(fed(words)).await.unwrap(); + } + closing(&cell, 5).await; + let asked = stub.asked(); + assert_eq!(asked[3]["turns"].as_array().unwrap().len(), 3); + assert_eq!( + asked[4]["turns"], + json!([{"prompt": "three", "response": "heard three. "}, {"prompt": "trim now", "response": "trimmed"}]), + "the two oldest turns are gone" + ); + } + + #[tokio::test] + async fn a_bridge_that_lingers_after_its_finish_does_not_hold_the_cell() { + let stub = Stub::new(); + let cell = raised(&stub).await; + cell.feed.send(fed("linger")).await.unwrap(); + cell.feed.send(fed("next")).await.unwrap(); + let tones = closing(&cell, 2).await; + assert_eq!(said(&tones), "lingeringheard next. "); + } + + #[tokio::test] + async fn a_bridge_that_falls_silent_but_lives_on_is_killed() { + let stub = Stub::new(); + let cell = raised(&stub).await; + cell.feed.send(fed("mute")).await.unwrap(); + cell.feed.send(fed("next")).await.unwrap(); + let tones = closing(&cell, 2).await; + let ends = ends(&tones); + assert_eq!(ends[0]["code"], "apple_fm_bridge_died"); + assert!(ends[0]["message"].as_str().unwrap().contains("killed"), "{}", ends[0]); + assert_eq!(ends[1]["reason"], "stop", "and the cell grows on"); + } + + #[tokio::test] + async fn what_goes_wrong_is_told_and_the_cell_grows_on() { + let stub = Stub::new(); + let cell = raised(&stub).await; + cell.feed.send(fed("boom")).await.unwrap(); + cell.feed.send(fed("die now")).await.unwrap(); + cell.feed.send(json!({"chi": "prompt", "sid": "s", "text": "look", "trellis": "json"})).await.unwrap(); + cell.feed.send(fed("fine")).await.unwrap(); + let tones = closing(&cell, 4).await; + let ends = ends(&tones); + assert_eq!(ends[0], json!({"chi": "error", "code": "apple_fm_guardrail_violation", "message": "the guardrail held"})); + assert_eq!(ends[1]["code"], "apple_fm_bridge_died"); + let died = ends[1]["message"].as_str().unwrap(); + assert!(died.contains("exit status: 3") && died.contains("the bridge fell over"), "{died}"); + assert_eq!(ends[2]["code"], "apple_fm_no_trellis"); + assert_eq!(ends[3]["reason"], "stop"); + assert_eq!(stub.asked().len(), 3, "the refused prompt never ran the bridge"); + assert_eq!(stub.asked()[2]["turns"], json!([]), "a bloom that failed is no turn"); + } + + #[tokio::test] + async fn a_missing_bridge_is_told() { + let nurse = AppleFm { bridge: "/nonexistent/apple-fm-gen".into() }; + let cell = nurse.raise(Egg::new(hum_identity::HumId::mint(), "m", "/tmp")).await.unwrap(); + cell.feed.send(fed("hi")).await.unwrap(); + assert_eq!(ends(&closing(&cell, 1).await)[0]["code"], "apple_fm_bridge_spawn"); + } +} diff --git a/hives/apple-fm/tests/common/mod.rs b/hives/apple-fm/tests/common/mod.rs new file mode 100644 index 0000000..b6710f8 --- /dev/null +++ b/hives/apple-fm/tests/common/mod.rs @@ -0,0 +1,260 @@ +//! The world apple-fm's nurse grows in: a humd in a root of its own, the +//! nurse as the process orchd would run, a bridge that stands in for +//! Apple's, and a forager that prompts through humd. +//! +//! The stand-in bridge answers `heard . `, or by the first word: +//! `hang` unfurls once and waits to be killed. It answers `--probe` with +//! the line in its `probe` file, or as available when there is none, and +//! keeps each request it was asked in `asked`. + +#![allow(dead_code)] + +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; +use std::time::{Duration, Instant}; + +use hum_thrum::{Bee, Event, Options}; +use serde_json::{json, Value}; +use thrum_core::Role; + +pub const WAIT: Duration = Duration::from_secs(30); + +pub struct World { + pub root: PathBuf, +} + +static WORLD: OnceLock = OnceLock::new(); + +pub fn world() -> &'static World { + WORLD.get_or_init(World::new) +} + +impl World { + fn new() -> World { + let root = tempfile::Builder::new().prefix("apple-fm").tempdir().unwrap().keep(); + unsafe { + std::env::set_var("XDG_STATE_HOME", root.join("s")); + std::env::set_var("XDG_CONFIG_HOME", root.join("c")); + std::env::set_var("XDG_DATA_HOME", root.join("d")); + std::env::set_var("XDG_CACHE_HOME", root.join("k")); + std::env::set_var("XDG_RUNTIME_DIR", root.join("r")); + std::env::set_var("HUM_LOG_LEVEL", "warn"); + std::env::remove_var("HUM_THRUM_SOCK"); + std::env::remove_var("HUM_SOCKET"); + } + std::fs::create_dir_all(hum_paths::hives_d_dir()).unwrap(); + std::fs::write(hum_paths::hum_json(), json!({"humd": {"metricsAddr": "none", "irohPort": 0}}).to_string()).unwrap(); + for hive in ["apple-fm", "probe"] { + std::fs::write(hum_paths::hive_installation(hive), "# apple-fm's test\n").unwrap(); + } + let sock = hum_paths::thrum_sock_resolved(); + let _ = std::fs::remove_file(&sock); + std::thread::spawn(|| { + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap(); + rt.block_on(async { + let mut cfg = humd::DaemonConfig::from_env(); + cfg.humd_key = None; + let _ = humd::run(cfg, std::future::pending::<()>()).await; + }); + }); + let since = Instant::now(); + while std::os::unix::net::UnixStream::connect(&sock).is_err() { + assert!(since.elapsed() < WAIT, "humd did not bind {}", sock.display()); + std::thread::sleep(Duration::from_millis(50)); + } + World { root } + } +} + +const STUB: &str = r#"#!/bin/sh +here="$(cd "$(dirname "$0")" && pwd)" +if [ "$1" = "--probe" ]; then + if [ -f "$here/probe" ]; then cat "$here/probe"; else echo '{"event":"probe","available":true}'; fi + exit 0 +fi +IFS= read -r line +printf '%s\n' "$line" >> "$here/asked" +echo $$ > "$here/pid" +words=$(printf '%s' "$line" | sed -n 's/.*"prompt":"\([^"]*\)"}$/\1/p') +case "$words" in + hang*) echo '{"event":"delta","text":"hanging"}'; exec sleep 30 ;; +esac +echo '{"event":"delta","text":"heard "}' +printf '{"event":"delta","text":"%s. "}\n' "$words" +echo '{"event":"finish","inputTokens":7,"outputTokens":3}' +"#; + +pub struct Stub { + dir: tempfile::TempDir, +} + +impl Stub { + pub fn new() -> Stub { + use std::os::unix::fs::PermissionsExt; + let dir = tempfile::tempdir().unwrap(); + let bridge = dir.path().join("apple-fm-gen"); + std::fs::write(&bridge, STUB).unwrap(); + std::fs::set_permissions(&bridge, std::fs::Permissions::from_mode(0o755)).unwrap(); + Stub { dir } + } + + pub fn bridge(&self) -> PathBuf { + self.dir.path().join("apple-fm-gen") + } + + pub fn probe(&self, available: bool, reason: &str) { + let line = json!({"event": "probe", "available": available, "reason": reason}); + std::fs::write(self.dir.path().join("probe"), format!("{line}\n")).unwrap(); + } + + pub fn asked(&self) -> Vec { + std::fs::read_to_string(self.dir.path().join("asked")) + .unwrap_or_default() + .lines() + .map(|l| serde_json::from_str(l).unwrap()) + .collect() + } + + pub fn alive(&self) -> bool { + let pid = std::fs::read_to_string(self.dir.path().join("pid")).unwrap(); + std::process::Command::new("kill") + .args(["-0", pid.trim()]) + .stderr(std::process::Stdio::null()) + .status() + .unwrap() + .success() + } +} + +pub struct Nurse { + child: tokio::process::Child, + pub model: String, + pub log: PathBuf, +} + +impl Nurse { + pub fn spawn(label: &str, bridge: &Path, env: &[(&str, &str)]) -> Nurse { + let w = world(); + let model = format!("apple-fm-{label}"); + let log = w.root.join(format!("nurse-{label}.log")); + let out = std::fs::File::create(&log).unwrap(); + let mut cmd = tokio::process::Command::new(env!("CARGO_BIN_EXE_apple-fm-nurse")); + cmd.env_remove("APPLE_FM_PROBE_MS") + .env("APPLE_FM_LABEL", label) + .env("APPLE_FM_MODEL", &model) + .env("APPLE_FM_BRIDGE", bridge) + .env("RUST_LOG", "info") + .env("NO_COLOR", "1") + .stdout(out.try_clone().unwrap()) + .stderr(out) + .kill_on_drop(true); + for (k, v) in env { + cmd.env(k, v); + } + Nurse { child: cmd.spawn().unwrap(), model, log } + } + + pub fn log_text(&self) -> String { + std::fs::read_to_string(&self.log).unwrap_or_default() + } + + pub async fn wait_for(&self, line: &str) { + let since = Instant::now(); + while !self.log_text().contains(line) { + assert!(since.elapsed() < WAIT, "no {line:?} in the nurse's log:\n{}", self.log_text()); + tokio::time::sleep(Duration::from_millis(25)).await; + } + } + + pub async fn nestled(self) -> Nurse { + self.wait_for("nurse.nestled").await; + self + } + + pub async fn exit(&mut self) -> std::process::ExitStatus { + tokio::time::timeout(WAIT, self.child.wait()).await.expect("the nurse exits").unwrap() + } +} + +pub struct Forager { + bee: Bee, +} + +impl Forager { + pub async fn link(label: &str) -> Forager { + world(); + let bee = Bee::link(Options::new("probe", label, Role::Forager).hello(json!({"version": "1.0.0"}))).await.unwrap(); + Forager { bee } + } + + pub async fn breathed(&mut self, model: &str) { + let since = Instant::now(); + loop { + assert!(since.elapsed() < WAIT, "humd never breathed that a nurse grows {model}"); + let tone = self.next().await; + if tone["chi"] == "breath" && tone["models"].as_array().is_some_and(|m| m.iter().any(|m| m["id"] == model)) { + return; + } + } + } + + pub fn send(&self, tone: Value) { + self.bee.send(tone); + } + + pub fn prompt(&self, sid: &str, model: &str, words: &str) { + self.send(json!({"chi": "prompt", "sid": sid, "modelId": model, "content": [{"kind": "text", "text": words}]})); + } + + pub async fn next(&mut self) -> Value { + loop { + match tokio::time::timeout(WAIT, self.bee.next()).await.expect("a tone in time").expect("the link lives") { + Event::Tone(tone) => return tone, + _ => continue, + } + } + } + + pub async fn on(&mut self, sid: &str) -> Value { + loop { + let tone = self.next().await; + if tone["sid"] == sid { + return tone; + } + } + } + + pub async fn bloom(&mut self, sid: &str) -> Bloom { + let mut bloom = Bloom::default(); + loop { + let tone = self.on(sid).await; + match tone["chi"].as_str() { + Some("unfurl") => { + if let Some(text) = tone["text"].as_str() { + bloom.said.push_str(text); + bloom.unfurls += 1; + } + } + Some("wilt") | Some("error") => { + bloom.end = tone; + return bloom; + } + _ => {} + } + } + } +} + +#[derive(Debug, Default)] +pub struct Bloom { + pub said: String, + pub unfurls: usize, + pub end: Value, +} + +impl Bloom { + pub fn ended(&self) -> String { + let end = &self.end; + format!("{} {}", end["chi"].as_str().unwrap_or(""), end["reason"].as_str().or(end["code"].as_str()).unwrap_or("")) + } +} diff --git a/hives/apple-fm/tests/live.rs b/hives/apple-fm/tests/live.rs new file mode 100644 index 0000000..abfc93e --- /dev/null +++ b/hives/apple-fm/tests/live.rs @@ -0,0 +1,96 @@ +//! The hive whole against Apple's own model: the nurse, the bridge that +//! `./build` makes, and a humd between them and a forager. +//! +//! ./build && cargo test --test live -- --ignored +//! +//! APPLE_FM_LIVE_BRIDGE names another bridge than target/release/apple-fm-gen. + +mod common; + +use std::path::PathBuf; +use std::time::{Duration, Instant}; + +use common::*; +use serde_json::{json, Value}; + +fn bridge() -> PathBuf { + let bridge = std::env::var("APPLE_FM_LIVE_BRIDGE") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target/release/apple-fm-gen")); + assert!(bridge.is_file(), "no bridge at {}; run ./build", bridge.display()); + bridge +} + +fn ask(sid: &str, model: &str, words: &str, more: Value) -> Value { + let mut tone = json!({"chi": "prompt", "sid": sid, "modelId": model, "content": [{"kind": "text", "text": words}]}); + for (k, v) in more.as_object().unwrap() { + tone[k] = v.clone(); + } + tone +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore = "needs macOS 26 with Apple Intelligence on, and the bridge ./build makes"] +async fn a_conversation_grows_through_apple_s_model() { + let nurse = Nurse::spawn("live", &bridge(), &[]); + let since = Instant::now(); + while !nurse.log_text().contains("nurse.nestled") { + let log = nurse.log_text(); + assert!(!log.contains("apple-fm.dormant") && !log.contains("apple-fm.never"), "Apple's model cannot run here:\n{log}"); + assert!(since.elapsed() < WAIT, "the nurse did not nestle:\n{log}"); + tokio::time::sleep(Duration::from_millis(50)).await; + } + let m = nurse.model.clone(); + let mut f = Forager::link("live").await; + f.breathed(&m).await; + + f.send(ask("talk", &m, "Reply with exactly one word: apple", json!({"temperature": 0.0}))); + let first = f.bloom("talk").await; + assert_eq!(first.ended(), "wilt stop", "{first:?}"); + assert!(first.said.to_lowercase().contains("apple"), "{first:?}"); + assert!(first.end["nectar"]["output_tokens"].as_u64().unwrap() > 0, "{first:?}"); + + f.send(ask("talk", &m, "Which fruit did you just name? Reply with that one word.", json!({"temperature": 0.0}))); + let second = f.bloom("talk").await; + assert!(second.said.to_lowercase().contains("apple"), "the cell kept the conversation through humd: {second:?}"); + + let count = "Count from 1 to 30, separated by commas, and nothing else."; + f.send(ask("count", &m, count, json!({"temperature": 0.0}))); + let counted = f.bloom("count").await; + assert!(counted.unfurls > 1, "the bloom streams: {counted:?}"); + assert_eq!(counted.said.matches("1, 2, 3,").count(), 1, "each unfurl is new words, not the bloom so far: {counted:?}"); + assert!(counted.said.contains("29, 30"), "{counted:?}"); + + f.send(ask("frost", &m, count, json!({"temperature": 0.0, "frost": [", 10"]}))); + let frosted = f.bloom("frost").await; + assert_eq!(frosted.ended(), "wilt stop", "{frosted:?}"); + assert!(frosted.said.trim_end().ends_with('9') && !frosted.said.contains("10"), "the bloom ends before its stop: {frosted:?}"); + + f.send(ask("season", &m, "Write a long story about a bee.", json!({"temperature": 0.0, "season": 12}))); + let seasoned = f.bloom("season").await; + assert_eq!(seasoned.ended(), "wilt max_tokens", "{seasoned:?}"); + assert_eq!(seasoned.end["nectar"]["output_tokens"], 12, "{seasoned:?}"); + + let filler = json!({ + "content": [{"kind": "text", "text": "Tell me about the history of beekeeping in Europe. ".repeat(45)}], + "grew": [{"kind": "text", "text": "Beekeeping in Europe goes back thousands of years. ".repeat(45)}], + }); + let mut graft = vec![filler; 5]; + graft.push(json!({ + "content": [{"kind": "text", "text": "Name a red fruit in one word."}], + "grew": [{"kind": "text", "text": "Strawberry"}], + })); + f.send(ask("long", &m, "Which fruit did you just name? Reply with that one word.", json!({"temperature": 0.0, "graft": graft}))); + let long = f.bloom("long").await; + assert_eq!(long.ended(), "wilt stop", "a conversation past the model's context still grows: {long:?}"); + assert!(long.said.to_lowercase().contains("strawberry"), "the newest turns are the ones kept: {long:?}"); + nurse.wait_for("apple-fm.trimmed dropped=").await; + + f.send(ask("cancel", &m, "Write a 2000 word essay about the history of beekeeping.", json!({}))); + let first = f.on("cancel").await; + assert_eq!(first["chi"], "unfurl", "{first}"); + let since = Instant::now(); + f.send(json!({"chi": "cancel", "sid": "cancel"})); + assert_eq!(f.bloom("cancel").await.ended(), "wilt cancel"); + assert!(since.elapsed() < Duration::from_secs(2), "a cancel ends the bloom at once: {:?}", since.elapsed()); +} diff --git a/hives/apple-fm/tests/nurse.rs b/hives/apple-fm/tests/nurse.rs new file mode 100644 index 0000000..379d3e9 --- /dev/null +++ b/hives/apple-fm/tests/nurse.rs @@ -0,0 +1,94 @@ +//! The nurse as humd reaches it: a forager's prompt goes through humd to +//! the nurse and its bloom comes back, a sigil keeps its conversation, a +//! cancel kills the bridge, and a nurse whose Mac cannot grow yet waits +//! before it nestles. + +mod common; + +use std::time::Duration; + +use common::*; +use serde_json::json; + +#[tokio::test(flavor = "multi_thread")] +async fn a_prompt_through_humd_unfurls_and_wilts_back() { + let stub = Stub::new(); + let nurse = Nurse::spawn("grow", &stub.bridge(), &[]).nestled().await; + let mut f = Forager::link("grow").await; + f.breathed(&nurse.model).await; + + f.send(json!({"chi": "prompt", "sid": "a", "modelId": nurse.model, "systemPrompt": "be brief", + "content": [{"kind": "text", "text": "hello"}]})); + let bloom = f.bloom("a").await; + assert_eq!((bloom.said.as_str(), bloom.unfurls), ("heard hello. ", 2), "each delta unfurls as it comes"); + assert_eq!(bloom.end["reason"], "stop"); + assert_eq!(bloom.end["nectar"], json!({"input_tokens": 7, "output_tokens": 3})); + + f.prompt("a", &nurse.model, "again"); + assert_eq!(f.bloom("a").await.said, "heard again. "); + let asked = stub.asked(); + assert_eq!(asked[0]["instructions"], "be brief"); + assert_eq!( + asked[1]["turns"], + json!([{"prompt": "hello", "response": "heard hello. "}]), + "the sigil's next prompt, sent with no graft, grows on from what its cell grew" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn a_cancel_through_humd_kills_the_bridge() { + let stub = Stub::new(); + let nurse = Nurse::spawn("cancel", &stub.bridge(), &[]).nestled().await; + let mut f = Forager::link("cancel").await; + f.breathed(&nurse.model).await; + + f.prompt("c", &nurse.model, "hang on"); + let first = f.on("c").await; + assert_eq!((first["chi"].clone(), first["text"].clone()), (json!("unfurl"), json!("hanging"))); + assert!(stub.alive()); + f.send(json!({"chi": "cancel", "sid": "c"})); + assert_eq!(f.bloom("c").await.ended(), "wilt cancel"); + tokio::time::sleep(Duration::from_millis(200)).await; + assert!(!stub.alive(), "the bridge is killed with its bloom"); + + f.prompt("c", &nurse.model, "after"); + assert_eq!(f.bloom("c").await.said, "heard after. ", "the sigil grows on in a fresh cell"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn a_nurse_waits_unnestled_until_apple_s_model_is_ready() { + let stub = Stub::new(); + stub.probe(false, "model_not_ready"); + let nurse = Nurse::spawn("dormant", &stub.bridge(), &[("APPLE_FM_PROBE_MS", "100")]); + nurse.wait_for("apple-fm.dormant").await; + tokio::time::sleep(Duration::from_millis(600)).await; + let log = nurse.log_text(); + assert!(!log.contains("nurse.nestled"), "a dormant nurse does not nestle:\n{log}"); + assert_eq!(log.matches("apple-fm.dormant").count(), 1, "it says so once, not at every probe:\n{log}"); + assert!(log.contains("reason=model_not_ready"), "{log}"); + + let mut f = Forager::link("dormant").await; + stub.probe(true, ""); + let nurse = nurse.nestled().await; + f.breathed(&nurse.model).await; + f.prompt("d", &nurse.model, "awake"); + assert_eq!(f.bloom("d").await.said, "heard awake. "); +} + +#[tokio::test(flavor = "multi_thread")] +async fn a_mac_that_cannot_run_the_model_keeps_its_unit_down() { + let stub = Stub::new(); + stub.probe(false, "device_not_eligible"); + let mut nurse = Nurse::spawn("never", &stub.bridge(), &[("APPLE_FM_PROBE_MS", "100")]); + assert!(nurse.exit().await.success(), "it exits 0, so orchd does not restart it:\n{}", nurse.log_text()); + let log = nurse.log_text(); + assert!(log.contains("apple-fm.never") && !log.contains("nurse.nestled"), "{log}"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn a_bridge_that_cannot_run_fails_the_unit() { + world(); + let mut nurse = Nurse::spawn("missing", std::path::Path::new("/nonexistent/apple-fm-gen"), &[]); + assert!(!nurse.exit().await.success(), "a missing bridge is an install gone wrong:\n{}", nurse.log_text()); + assert!(nurse.log_text().contains("/nonexistent/apple-fm-gen"), "{}", nurse.log_text()); +} diff --git a/release.sh b/release.sh index eabe2f0..7350278 100755 --- a/release.sh +++ b/release.sh @@ -19,6 +19,7 @@ set -euo pipefail # what `hum hive install` publishes, so those track the same number. EXTRA_VERSIONED_FILES=( hives/anthropic-server/package.json + hives/apple-fm/Cargo.toml hives/bash/Cargo.toml hives/bp7/Cargo.toml hives/claude-cli/Cargo.toml