From f613435e7097a693db3c538f1f5c81c65c16f800 Mon Sep 17 00:00:00 2001 From: GhostFrame Date: Mon, 6 Jul 2026 12:43:20 -0400 Subject: [PATCH] feat(embed): candle-backed semantic embeddings behind an embeddings feature New frameshift-embed-candle crate: CandleEmbedder loads sentence-transformers/all-MiniLM-L6-v2 via hf-hub (download on first use, mmap thereafter), tokenizes with truncation at BERT's window, mean-pools and L2-normalizes into unit vectors implementing the orchestrator's Embedder trait. Degenerate inputs and inference failures return an empty vector (no semantic signal), never an error. Wiring: select_rich gains a _with_embedder variant; the CLI select command, MCP frameshift_select, and the daemon evaluation loop all use the embedder behind an off-by-default 'embeddings' cargo feature, with a process-wide once-loaded model in the daemon and MCP server. Model load failure degrades to lexical-only ranking with a warning. Verified end to end: the ignored model test downloads the real model and confirms related text out-scores unrelated text. --- Cargo.lock | 1133 +++++++++++++++++- Cargo.toml | 1 + crates/frameshift-cli/Cargo.toml | 6 + crates/frameshift-cli/src/cmd/select.rs | 34 +- crates/frameshift-daemon/Cargo.toml | 6 + crates/frameshift-daemon/src/orchestrator.rs | 32 +- crates/frameshift-embed-candle/Cargo.toml | 21 + crates/frameshift-embed-candle/src/lib.rs | 246 ++++ crates/frameshift-mcp/Cargo.toml | 6 + crates/frameshift-mcp/src/tools.rs | 33 +- crates/frameshift-orchestrator/src/lib.rs | 4 +- crates/frameshift-orchestrator/src/run.rs | 16 +- 12 files changed, 1511 insertions(+), 27 deletions(-) create mode 100644 crates/frameshift-embed-candle/Cargo.toml create mode 100644 crates/frameshift-embed-candle/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3322ab2..6640bd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,9 @@ 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", ] @@ -307,6 +309,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -352,6 +360,21 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[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 = "1.3.2" @@ -476,6 +499,20 @@ name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] name = "byteorder" @@ -489,6 +526,76 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +[[package]] +name = "candle-core" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ecb245093b0f791b89d3420c3df9c6d49c60ab63ba54db896bf8a3baf486706" +dependencies = [ + "byteorder", + "float8", + "gemm", + "half", + "libc", + "libm", + "memmap2", + "num-traits", + "num_cpus", + "rand 0.9.4", + "rand_distr", + "rayon", + "safetensors", + "thiserror 2.0.18", + "tokenizers 0.22.2", + "yoke", + "zerocopy", + "zip", +] + +[[package]] +name = "candle-nn" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa10b6ccc365b33210ce404fbf45e60d3e0bdac1004463cf1052e6ee1c1739a" +dependencies = [ + "candle-core", + "half", + "libc", + "num-traits", + "rayon", + "safetensors", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "candle-transformers" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcbbf7ff00ff6fe2af22b93600195917fe90e90ff48424a140d1a926c44b1c1" +dependencies = [ + "byteorder", + "candle-core", + "candle-nn", + "fancy-regex", + "num-traits", + "rand 0.9.4", + "rayon", + "serde", + "serde_json", + "serde_plain", + "tracing", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.65" @@ -623,6 +730,21 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + [[package]] name = "compression-codecs" version = "0.4.38" @@ -640,6 +762,18 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -652,6 +786,17 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "cookie-factory" version = "0.3.3" @@ -661,6 +806,34 @@ dependencies = [ "futures", ] +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -704,12 +877,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4279b0f31df31b4add8aeae57d40f3b5e53aad2b531e89b982bd75ed1898851d" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -765,6 +963,22 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "daachorse" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f55d7153ba3b507595872a3874803f07a8a81d1e888abed8e5db7da0597d6e2" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + [[package]] name = "darling" version = "0.21.3" @@ -785,6 +999,20 @@ dependencies = [ "darling_macro 0.23.0", ] +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.118", +] + [[package]] name = "darling_core" version = "0.21.3" @@ -812,6 +1040,17 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.118", +] + [[package]] name = "darling_macro" version = "0.21.3" @@ -834,6 +1073,15 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "dary_heap" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe" +dependencies = [ + "serde", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -912,6 +1160,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -921,6 +1179,37 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.118", +] + [[package]] name = "diesel" version = "2.3.10" @@ -1009,6 +1298,27 @@ dependencies = [ "ctutils", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + [[package]] name = "displaydoc" version = "0.2.6" @@ -1031,6 +1341,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "2.0.2" @@ -1057,6 +1376,22 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dyn-stack" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8" +dependencies = [ + "bytemuck", + "dyn-stack-macros", +] + +[[package]] +name = "dyn-stack-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" + [[package]] name = "ed25519" version = "2.2.3" @@ -1088,6 +1423,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1097,6 +1438,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1113,6 +1466,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +dependencies = [ + "cc", +] + [[package]] name = "etcetera" version = "0.11.0" @@ -1141,6 +1503,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[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.4.1" @@ -1213,6 +1586,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float8" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d1f04709a8ac06e8e8042875a3c466cc4832d3c1a18dbcb9dba3c6e83046bc" +dependencies = [ + "half", + "num-traits", + "rand 0.9.4", + "rand_distr", +] + [[package]] name = "fluent" version = "0.16.1" @@ -1269,6 +1654,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1341,6 +1741,7 @@ dependencies = [ "clap", "frameshift-client", "frameshift-conformance", + "frameshift-embed-candle", "frameshift-growth", "frameshift-orchestrator", "frameshift-source", @@ -1407,6 +1808,7 @@ name = "frameshift-daemon" version = "0.1.0" dependencies = [ "frameshift-client", + "frameshift-embed-candle", "frameshift-growth", "frameshift-orchestrator", "libc", @@ -1420,6 +1822,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "frameshift-embed-candle" +version = "0.1.0" +dependencies = [ + "candle-core", + "candle-nn", + "candle-transformers", + "frameshift-orchestrator", + "hf-hub", + "serde_json", + "thiserror 2.0.18", + "tokenizers 0.23.1", + "tracing", +] + [[package]] name = "frameshift-growth" version = "0.1.0" @@ -1436,6 +1853,7 @@ version = "0.1.0" dependencies = [ "frameshift-capabilities", "frameshift-client", + "frameshift-embed-candle", "frameshift-growth", "frameshift-orchestrator", "frameshift-pack", @@ -1784,6 +2202,125 @@ dependencies = [ "slab", ] +[[package]] +name = "gemm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa0673db364b12263d103b68337a68fbecc541d6f6b61ba72fe438654709eacb" +dependencies = [ + "dyn-stack", + "gemm-c32", + "gemm-c64", + "gemm-common", + "gemm-f16", + "gemm-f32", + "gemm-f64", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086936dbdcb99e37aad81d320f98f670e53c1e55a98bee70573e83f95beb128c" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c8aeeeec425959bda4d9827664029ba1501a90a0d1e6228e48bef741db3a3f" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-common" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88027625910cc9b1085aaaa1c4bc46bb3a36aad323452b33c25b5e4e7c8e2a3e" +dependencies = [ + "bytemuck", + "dyn-stack", + "half", + "libm", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp", + "raw-cpuid", + "rayon", + "seq-macro", + "sysctl", +] + +[[package]] +name = "gemm-f16" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3df7a55202e6cd6739d82ae3399c8e0c7e1402859b30e4cb780e61525d9486e" +dependencies = [ + "dyn-stack", + "gemm-common", + "gemm-f32", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0b8c9da1fbec6e3e3ab2ce6bc259ef18eb5f6f0d3e4edf54b75f9fd41a81c" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056131e8f2a521bfab322f804ccd652520c79700d81209e9d9275bbdecaadc6a" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1875,6 +2412,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", + "num-traits", + "rand 0.9.4", + "rand_distr", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1899,6 +2451,8 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", + "serde", + "serde_core", ] [[package]] @@ -1934,6 +2488,30 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hf-hub" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef3982638978efa195ff11b305f51f1f22f4f0a6cabee7af79b383ebee6a213" +dependencies = [ + "dirs", + "futures", + "http", + "indicatif", + "libc", + "log", + "native-tls", + "num_cpus", + "rand 0.9.4", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "ureq 3.3.0", + "windows-sys 0.61.2", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -2097,6 +2675,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -2115,9 +2709,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2360,6 +2956,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indicatif" +version = "0.18.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "inlinable_string" version = "0.1.15" @@ -2490,6 +3099,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libmimalloc-sys" version = "0.1.49" @@ -2537,6 +3152,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2558,6 +3179,22 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "macro_rules_attribute" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" + [[package]] name = "matchers" version = "0.2.0" @@ -2599,6 +3236,16 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", + "stable_deref_trait", +] + [[package]] name = "migrations_internals" version = "2.3.0" @@ -2674,6 +3321,28 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "monostate" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "multer" version = "3.1.0" @@ -2691,6 +3360,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -2769,6 +3455,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] @@ -2816,6 +3503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2885,28 +3573,93 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "onig" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" +dependencies = [ + "bitflags 2.13.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "openssl-sys" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] -name = "openssl-probe" -version = "0.2.1" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" @@ -2956,6 +3709,12 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -2989,6 +3748,15 @@ dependencies = [ "syn 2.0.118", ] +[[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" @@ -3046,7 +3814,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", + "der 0.7.10", "spki", ] @@ -3229,6 +3997,29 @@ dependencies = [ "prost", ] +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + [[package]] name = "quanta" version = "0.12.6" @@ -3406,6 +4197,16 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.4", +] + [[package]] name = "raw-cpuid" version = "11.6.0" @@ -3415,6 +4216,43 @@ dependencies = [ "bitflags 2.13.0", ] +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-cond" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f" +dependencies = [ + "either", + "itertools", + "rayon", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3424,6 +4262,17 @@ dependencies = [ "bitflags 2.13.0", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -3481,6 +4330,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", "futures-util", "h2", @@ -3489,9 +4339,12 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -3503,6 +4356,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -3685,6 +4539,19 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "safetensors" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b079b829cb27a1c3c374341345ed2e8b2c0c839034522cee576c140bd7f846" +dependencies = [ + "hashbrown 0.16.1", + "libc", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "salsa20" version = "0.10.2" @@ -3770,7 +4637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3807,6 +4674,12 @@ version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + [[package]] name = "serde" version = "1.0.228" @@ -3861,6 +4734,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -4024,6 +4906,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.9.8" @@ -4046,7 +4939,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", +] + +[[package]] +name = "spm_precompiled" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" +dependencies = [ + "base64 0.13.1", + "nom", + "serde", + "unicode-segmentation", ] [[package]] @@ -4055,6 +4960,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.5" @@ -4148,6 +5059,41 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.13.0", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tar" version = "0.4.46" @@ -4317,6 +5263,73 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokenizers" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223" +dependencies = [ + "ahash", + "aho-corasick", + "compact_str", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.4", + "itertools", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.4", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror 2.0.18", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + +[[package]] +name = "tokenizers" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e5bea67576e04b6ff8564c5d9e09c2ef0cf476502245f2f120e497769d3112" +dependencies = [ + "ahash", + "compact_str", + "daachorse", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.4", + "indicatif", + "itertools", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.4", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror 2.0.18", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + [[package]] name = "tokio" version = "1.52.3" @@ -4345,6 +5358,16 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-postgres" version = "0.7.18" @@ -4689,6 +5712,12 @@ dependencies = [ "rustc-hash 2.1.2", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.20.1" @@ -4744,12 +5773,45 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-normalization-alignments" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" +dependencies = [ + "smallvec", +] + [[package]] name = "unicode-properties" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "universal-hash" version = "0.5.1" @@ -4791,12 +5853,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ "base64 0.22.1", + "cookie_store", + "der 0.8.0", + "flate2", "log", + "native-tls", "percent-encoding", "rustls", "rustls-pki-types", + "serde", + "serde_json", + "socks", "ureq-proto", "utf8-zero", + "webpki-root-certs", + "webpki-roots 1.0.8", ] [[package]] @@ -5012,6 +6083,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -5115,6 +6195,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -5560,6 +6651,18 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "indexmap 2.14.0", + "memchr", + "typed-path", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 8fe8b2b..9225b27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/frameshift-compose", "crates/frameshift-conformance", "crates/frameshift-daemon", + "crates/frameshift-embed-candle", "crates/frameshift-growth", "crates/frameshift-mcp", "crates/frameshift-memory", diff --git a/crates/frameshift-cli/Cargo.toml b/crates/frameshift-cli/Cargo.toml index d912040..2ca9f3f 100644 --- a/crates/frameshift-cli/Cargo.toml +++ b/crates/frameshift-cli/Cargo.toml @@ -16,10 +16,16 @@ frameshift-source = { path = "../frameshift-source" } frameshift-growth = { path = "../frameshift-growth" } frameshift-conformance = { path = "../frameshift-conformance" } frameshift-orchestrator = { path = "../frameshift-orchestrator" } +frameshift-embed-candle = { path = "../frameshift-embed-candle", optional = true } clap = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } +[features] +# Semantic selection via a local candle sentence-embedding model. Off by +# default so the standard build stays free of the ML dependency stack. +embeddings = ["dep:frameshift-embed-candle"] + [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/frameshift-cli/src/cmd/select.rs b/crates/frameshift-cli/src/cmd/select.rs index 91a8d70..f5d622b 100644 --- a/crates/frameshift-cli/src/cmd/select.rs +++ b/crates/frameshift-cli/src/cmd/select.rs @@ -7,10 +7,31 @@ use std::path::PathBuf; use clap::Args; use frameshift_client::Client; -use frameshift_orchestrator::{PolicyWeights, Preferences, SelectionInputs}; +use frameshift_orchestrator::{Embedder, PolicyWeights, Preferences, SelectionInputs}; use crate::util::CliError; +/// Build the semantic embedder when the `embeddings` feature is enabled. +/// +/// Model download or load failures degrade to `None` with a stderr warning, +/// so selection falls back to the lexical channels instead of failing. +#[cfg(feature = "embeddings")] +fn make_embedder() -> Option> { + match frameshift_embed_candle::CandleEmbedder::from_hub() { + Ok(embedder) => Some(Box::new(embedder)), + Err(e) => { + eprintln!("warning: semantic embeddings unavailable ({e}); using lexical ranking only"); + None + } + } +} + +/// Without the `embeddings` feature there is never an embedder. +#[cfg(not(feature = "embeddings"))] +fn make_embedder() -> Option> { + None +} + /// Arguments for the `select` subcommand. #[derive(Debug, Args)] pub struct SelectArgs { @@ -72,17 +93,22 @@ pub fn run_select(client: &Client, args: SelectArgs) -> Result<(), CliError> { weights: PolicyWeights::default(), }; + // Semantic channel: present only when built with the `embeddings` feature + // and the model loads; otherwise ranking is purely lexical/contextual. + let embedder = make_embedder(); + if args.format == "json" { // Emit the full SelectionOutput as structured JSON. - let output = frameshift_orchestrator::select_rich(&inputs) - .map_err(|e| CliError::Orchestrator(e.to_string()))?; + let output = + frameshift_orchestrator::select_rich_with_embedder(&inputs, embedder.as_deref()) + .map_err(|e| CliError::Orchestrator(e.to_string()))?; let json = serde_json::to_string_pretty(&output)?; println!("{}", json); return Ok(()); } // Default: table format using the ranked candidate list. - let ranked = frameshift_orchestrator::select(&inputs) + let ranked = frameshift_orchestrator::select_with_embedder(&inputs, embedder.as_deref()) .map_err(|e| CliError::Orchestrator(e.to_string()))?; if ranked.is_empty() { diff --git a/crates/frameshift-daemon/Cargo.toml b/crates/frameshift-daemon/Cargo.toml index 0d1d372..786aff1 100644 --- a/crates/frameshift-daemon/Cargo.toml +++ b/crates/frameshift-daemon/Cargo.toml @@ -14,6 +14,7 @@ path = "src/main.rs" frameshift-client = { path = "../frameshift-client" } frameshift-growth = { path = "../frameshift-growth" } frameshift-orchestrator = { path = "../frameshift-orchestrator" } +frameshift-embed-candle = { path = "../frameshift-embed-candle", optional = true } serde.workspace = true serde_json.workspace = true tokio = { workspace = true, features = ["full"] } @@ -26,3 +27,8 @@ libc = "0.2" [dev-dependencies] tempfile.workspace = true + +[features] +# Semantic selection via a local candle sentence-embedding model. Off by +# default so the standard build stays free of the ML dependency stack. +embeddings = ["dep:frameshift-embed-candle"] diff --git a/crates/frameshift-daemon/src/orchestrator.rs b/crates/frameshift-daemon/src/orchestrator.rs index faef248..ae2919f 100644 --- a/crates/frameshift-daemon/src/orchestrator.rs +++ b/crates/frameshift-daemon/src/orchestrator.rs @@ -11,12 +11,40 @@ use frameshift_client::Client; use frameshift_orchestrator::{ audit::{now_timestamp, AuditLog, Transition}, controller::{Decision, SwitchController, SwitchPolicy}, + embed::Embedder, feedback::Preferences, mode::{Mode, ModeState}, policy::PolicyWeights, - run::{select, SelectionInputs}, + run::{select_with_embedder, SelectionInputs}, }; +/// Return the process-wide semantic embedder, loading the model once on first +/// use. A failed load (offline, corrupt cache) is remembered as `None` so the +/// daemon does not retry the download every evaluation tick. +#[cfg(feature = "embeddings")] +fn shared_embedder() -> Option<&'static dyn Embedder> { + use std::sync::OnceLock; + static EMBEDDER: OnceLock> = OnceLock::new(); + EMBEDDER + .get_or_init( + || match frameshift_embed_candle::CandleEmbedder::from_hub() { + Ok(e) => Some(e), + Err(e) => { + tracing::warn!(error = %e, "semantic embeddings unavailable; lexical ranking only"); + None + } + }, + ) + .as_ref() + .map(|e| e as &dyn Embedder) +} + +/// Without the `embeddings` feature there is never an embedder. +#[cfg(not(feature = "embeddings"))] +fn shared_embedder() -> Option<&'static dyn Embedder> { + None +} + /// Read the persona name recorded in the project's active marker, if any. /// /// Returns `None` when the marker is absent, unreadable, or empty after @@ -123,7 +151,7 @@ pub fn evaluate_and_apply(client: &Client, controller: &mut SwitchController, pr weights: PolicyWeights::default(), }; - let ranked = match select(&inputs) { + let ranked = match select_with_embedder(&inputs, shared_embedder()) { Ok(r) => r, Err(e) => { tracing::warn!(error = %e, "orchestrator: selection failed"); diff --git a/crates/frameshift-embed-candle/Cargo.toml b/crates/frameshift-embed-candle/Cargo.toml new file mode 100644 index 0000000..44d3a4e --- /dev/null +++ b/crates/frameshift-embed-candle/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "frameshift-embed-candle" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +description = "Candle-powered sentence embeddings (all-MiniLM-L6-v2) implementing the orchestrator's Embedder trait for semantic persona selection." + +[dependencies] +frameshift-orchestrator = { path = "../frameshift-orchestrator" } +# Pure-Rust inference stack: no native ONNX runtime to package per platform. +candle-core = "0.11" +candle-nn = "0.11" +candle-transformers = "0.11" +# HuggingFace tokenizer files (tokenizer.json) for the BERT wordpiece vocab. +tokenizers = "0.23" +# Model download-on-first-use with a local cache shared across projects. +hf-hub = "0.5" +serde_json = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } diff --git a/crates/frameshift-embed-candle/src/lib.rs b/crates/frameshift-embed-candle/src/lib.rs new file mode 100644 index 0000000..d16920a --- /dev/null +++ b/crates/frameshift-embed-candle/src/lib.rs @@ -0,0 +1,246 @@ +//! Real semantic embeddings for persona selection, powered by candle. +//! +//! This is Phase 2 of the semantic-selection arc: a concrete [`Embedder`] +//! backed by a small sentence-transformer (`all-MiniLM-L6-v2`, ~23 MB of +//! safetensors) running on pure-Rust candle. The model is downloaded on first +//! use through `hf-hub` into its standard local cache and memory-mapped on +//! subsequent loads, so no network access happens after the first run. +//! +//! Everything here is optional: the workspace only compiles this crate behind +//! the `embeddings` cargo feature of its consumers, and every failure path +//! (no network, corrupt cache, tokenizer errors) degrades to "no embedder", +//! which the selection engine treats as a zero semantic signal. + +use std::path::Path; + +use candle_core::{Device, Tensor}; +use candle_nn::VarBuilder; +use candle_transformers::models::bert::{BertModel, Config, DTYPE}; +use hf_hub::api::sync::Api; +use hf_hub::{Repo, RepoType}; +use tokenizers::Tokenizer; + +use frameshift_orchestrator::Embedder; + +/// The default sentence-embedding model: small, permissively licensed, and the +/// de-facto standard for lightweight sentence similarity. +pub const DEFAULT_MODEL_ID: &str = "sentence-transformers/all-MiniLM-L6-v2"; + +/// BERT's positional-embedding window; longer inputs are truncated by the +/// tokenizer rather than erroring at the tensor layer. +const MAX_TOKENS: usize = 512; + +/// Errors constructing or loading a [`CandleEmbedder`]. +/// +/// Kept deliberately coarse: callers only branch on "embedder available or +/// not", so each variant just preserves the failing layer for diagnostics. +#[derive(Debug, thiserror::Error)] +pub enum EmbedError { + /// Downloading model artifacts from the hub failed (offline, DNS, 404). + #[error("model download failed: {0}")] + Download(String), + + /// Reading or parsing a model artifact from disk failed. + #[error("model artifact unreadable: {0}")] + Artifact(String), + + /// Building the tokenizer from tokenizer.json failed. + #[error("tokenizer load failed: {0}")] + Tokenizer(String), + + /// Constructing the BERT model from its weights failed. + #[error("model load failed: {0}")] + Model(String), +} + +/// A sentence embedder backed by a candle BERT model. +/// +/// Construction is the expensive step (download on first use, then an mmap +/// load); [`Embedder::embed`] calls are cheap enough for selection-time use. +/// The struct is immutable after construction, so `&self` embedding calls are +/// safe to share across threads. +pub struct CandleEmbedder { + /// The loaded BERT encoder. + model: BertModel, + /// WordPiece tokenizer configured to truncate at [`MAX_TOKENS`]. + tokenizer: Tokenizer, + /// Inference device; always CPU -- selection is low-frequency and small. + device: Device, +} + +impl CandleEmbedder { + /// Load the default model ([`DEFAULT_MODEL_ID`]), downloading it into the + /// hf-hub cache on first use. + pub fn from_hub() -> Result { + Self::from_hub_model(DEFAULT_MODEL_ID) + } + + /// Load `model_id` from the hub cache, downloading on first use. + /// + /// The model must ship `config.json`, `tokenizer.json`, and + /// `model.safetensors` (the standard sentence-transformers layout). + pub fn from_hub_model(model_id: &str) -> Result { + let api = Api::new().map_err(|e| EmbedError::Download(e.to_string()))?; + let repo = api.repo(Repo::new(model_id.to_string(), RepoType::Model)); + let config = repo + .get("config.json") + .map_err(|e| EmbedError::Download(e.to_string()))?; + let tokenizer = repo + .get("tokenizer.json") + .map_err(|e| EmbedError::Download(e.to_string()))?; + let weights = repo + .get("model.safetensors") + .map_err(|e| EmbedError::Download(e.to_string()))?; + Self::from_files(&config, &tokenizer, &weights) + } + + /// Build an embedder from already-downloaded artifact files. + /// + /// Useful for bundled distributions (desktop app) and offline tests; no + /// network access is attempted. + pub fn from_files( + config_path: &Path, + tokenizer_path: &Path, + weights_path: &Path, + ) -> Result { + let config_raw = std::fs::read_to_string(config_path) + .map_err(|e| EmbedError::Artifact(format!("{}: {e}", config_path.display())))?; + let config: Config = + serde_json::from_str(&config_raw).map_err(|e| EmbedError::Artifact(e.to_string()))?; + + let mut tokenizer = Tokenizer::from_file(tokenizer_path) + .map_err(|e| EmbedError::Tokenizer(e.to_string()))?; + // Truncate at the model's positional window so arbitrarily long task + // hints embed instead of erroring; no padding needed for single texts. + let truncation = tokenizers::TruncationParams { + max_length: MAX_TOKENS, + ..Default::default() + }; + tokenizer + .with_truncation(Some(truncation)) + .map_err(|e| EmbedError::Tokenizer(e.to_string()))?; + + let device = Device::Cpu; + // SAFETY: mmap of the safetensors file; the file comes from the local + // cache we just resolved and is not mutated while mapped. + let vb = unsafe { + VarBuilder::from_mmaped_safetensors(&[weights_path], DTYPE, &device) + .map_err(|e| EmbedError::Model(e.to_string()))? + }; + let model = BertModel::load(vb, &config).map_err(|e| EmbedError::Model(e.to_string()))?; + + Ok(CandleEmbedder { + model, + tokenizer, + device, + }) + } + + /// Tokenize, run the encoder, mean-pool over the sequence, and + /// L2-normalize into a unit vector. + fn embed_inner(&self, text: &str) -> Result, candle_core::Error> { + let encoding = self + .tokenizer + .encode(text, true) + .map_err(|e| candle_core::Error::Msg(e.to_string()))?; + let ids = encoding.get_ids(); + if ids.is_empty() { + return Ok(Vec::new()); + } + + let token_ids = Tensor::new(ids, &self.device)?.unsqueeze(0)?; + let token_type_ids = token_ids.zeros_like()?; + // Single unpadded sequence: a None attention mask means "attend to + // everything", which is exactly right without padding tokens. + let hidden = self.model.forward(&token_ids, &token_type_ids, None)?; + + // Mean-pool the (1, seq, hidden) activations over the sequence axis. + let (_batch, seq_len, _hidden) = hidden.dims3()?; + let pooled = (hidden.sum(1)? / (seq_len as f64))?; + let vector = pooled.squeeze(0)?.to_vec1::()?; + Ok(l2_normalized(vector)) + } +} + +impl Embedder for CandleEmbedder { + /// Embed `text` into a unit vector; degenerate inputs and inference + /// failures return an empty vector, which the scorer reads as "no + /// semantic signal" rather than an error. + fn embed(&self, text: &str) -> Vec { + let trimmed = text.trim(); + if trimmed.is_empty() { + return Vec::new(); + } + match self.embed_inner(trimmed) { + Ok(v) => v, + Err(e) => { + tracing::warn!(error = %e, "semantic embedding failed; returning no signal"); + Vec::new() + } + } + } +} + +/// L2-normalize `v` in plain Rust, returning the unit vector. +/// +/// A zero (or effectively zero) norm returns the input unchanged rather than +/// dividing by zero; cosine similarity downstream already treats such vectors +/// as no-signal. +fn l2_normalized(mut v: Vec) -> Vec { + let norm = v.iter().map(|x| x * x).sum::().sqrt(); + if norm > f32::EPSILON { + for x in &mut v { + *x /= norm; + } + } + v +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A normalized vector has unit length. + #[test] + fn l2_normalized_produces_unit_vector() { + let v = l2_normalized(vec![3.0, 4.0]); + assert!((v[0] - 0.6).abs() < 1e-6); + assert!((v[1] - 0.8).abs() < 1e-6); + let norm: f32 = v.iter().map(|x| x * x).sum::().sqrt(); + assert!((norm - 1.0).abs() < 1e-6); + } + + /// A zero vector passes through unchanged instead of dividing by zero. + #[test] + fn l2_normalized_zero_vector_is_unchanged() { + let v = l2_normalized(vec![0.0, 0.0, 0.0]); + assert_eq!(v, vec![0.0, 0.0, 0.0]); + assert!(v.iter().all(|x| x.is_finite())); + } + + /// An empty vector stays empty. + #[test] + fn l2_normalized_empty_is_empty() { + assert!(l2_normalized(Vec::new()).is_empty()); + } + + /// End-to-end embedding sanity: related texts score above unrelated ones. + /// + /// Ignored by default because it downloads the model on first run; run + /// explicitly with `cargo test -p frameshift-embed-candle -- --ignored`. + #[test] + #[ignore = "downloads the ~23MB model on first run"] + fn embeds_and_ranks_related_text_higher() { + let embedder = CandleEmbedder::from_hub().expect("model load"); + let task = embedder.embed("fix a memory safety bug in the borrow checker"); + let related = embedder.embed("debug rust ownership and lifetime errors"); + let unrelated = embedder.embed("bake a chocolate cake with vanilla frosting"); + assert_eq!(task.len(), 384, "MiniLM-L6 has 384-dim embeddings"); + + let sim = |a: &[f32], b: &[f32]| -> f32 { a.iter().zip(b).map(|(x, y)| x * y).sum() }; + assert!( + sim(&task, &related) > sim(&task, &unrelated), + "related text must out-score unrelated text" + ); + } +} diff --git a/crates/frameshift-mcp/Cargo.toml b/crates/frameshift-mcp/Cargo.toml index 1c2c293..91bdf4d 100644 --- a/crates/frameshift-mcp/Cargo.toml +++ b/crates/frameshift-mcp/Cargo.toml @@ -15,6 +15,7 @@ frameshift-capabilities = { path = "../frameshift-capabilities" } frameshift-client = { path = "../frameshift-client" } frameshift-growth = { path = "../frameshift-growth" } frameshift-orchestrator = { path = "../frameshift-orchestrator" } +frameshift-embed-candle = { path = "../frameshift-embed-candle", optional = true } frameshift-pack = { path = "../frameshift-pack" } serde.workspace = true serde_json.workspace = true @@ -26,3 +27,8 @@ tracing-subscriber.workspace = true [dev-dependencies] tempfile.workspace = true + +[features] +# Semantic selection via a local candle sentence-embedding model. Off by +# default so the standard build stays free of the ML dependency stack. +embeddings = ["dep:frameshift-embed-candle"] diff --git a/crates/frameshift-mcp/src/tools.rs b/crates/frameshift-mcp/src/tools.rs index 5bce5e4..8c35c29 100644 --- a/crates/frameshift-mcp/src/tools.rs +++ b/crates/frameshift-mcp/src/tools.rs @@ -4,12 +4,41 @@ use frameshift_capabilities::{CapabilityFilter, Tool as CapabilityTool}; use frameshift_client::{Client, InstallRequest, InstallSource, PersonaSpec}; use frameshift_orchestrator::{ - AuditLog, Mode, ModeState, PolicyWeights, Preferences, SelectionInputs, + AuditLog, Embedder, Mode, ModeState, PolicyWeights, Preferences, SelectionInputs, }; use frameshift_pack::{CapabilityManifest, PackManifest}; use crate::protocol::{ToolContent, ToolDef, ToolResult}; +/// Return the process-wide semantic embedder, loading the model once on first +/// use. A failed load (offline, corrupt cache) is remembered as `None` so +/// repeated `frameshift_select` calls do not retry the download. +#[cfg(feature = "embeddings")] +fn shared_embedder() -> Option<&'static dyn Embedder> { + use std::sync::OnceLock; + static EMBEDDER: OnceLock> = OnceLock::new(); + EMBEDDER + .get_or_init( + || match frameshift_embed_candle::CandleEmbedder::from_hub() { + Ok(e) => Some(e), + Err(e) => { + eprintln!( + "warning: semantic embeddings unavailable ({e}); lexical ranking only" + ); + None + } + }, + ) + .as_ref() + .map(|e| e as &dyn Embedder) +} + +/// Without the `embeddings` feature there is never an embedder. +#[cfg(not(feature = "embeddings"))] +fn shared_embedder() -> Option<&'static dyn Embedder> { + None +} + /// Return the complete list of available MCP tools with their JSON Schema definitions. pub fn tool_definitions() -> Vec { vec![ @@ -566,7 +595,7 @@ fn call_select(arguments: &serde_json::Value, client: &Client) -> ToolResult { weights: PolicyWeights::default(), }; - let ranked = match frameshift_orchestrator::select(&inputs) { + let ranked = match frameshift_orchestrator::select_with_embedder(&inputs, shared_embedder()) { Ok(r) => r, Err(e) => return err_result(format!("selection failed: {}", e)), }; diff --git a/crates/frameshift-orchestrator/src/lib.rs b/crates/frameshift-orchestrator/src/lib.rs index ca18e32..668ffd4 100644 --- a/crates/frameshift-orchestrator/src/lib.rs +++ b/crates/frameshift-orchestrator/src/lib.rs @@ -36,8 +36,8 @@ pub use intent::{classify as classify_intent, Intent}; pub use mode::{Mode, ModeState}; pub use policy::{rank, rank_with_embedder, PolicyWeights, ScoreComponents, Scored}; pub use run::{ - select, select_rich, select_with_embedder, CandidateOutput, ContextSnapshot, SelectionInputs, - SelectionOutput, + select, select_rich, select_rich_with_embedder, select_with_embedder, CandidateOutput, + ContextSnapshot, SelectionInputs, SelectionOutput, }; /// Facade that wires together the index, weights, policy, preferences, and controller. diff --git a/crates/frameshift-orchestrator/src/run.rs b/crates/frameshift-orchestrator/src/run.rs index 2ce7c14..48fdccf 100644 --- a/crates/frameshift-orchestrator/src/run.rs +++ b/crates/frameshift-orchestrator/src/run.rs @@ -30,7 +30,7 @@ use crate::error::OrchestratorError; use crate::feedback::Preferences; use crate::index::PersonaIndex; use crate::intent::Intent; -use crate::policy::{rank, rank_with_embedder, PolicyWeights, Scored}; +use crate::policy::{rank_with_embedder, PolicyWeights, Scored}; /// All inputs required to run a persona selection pass. pub struct SelectionInputs<'a> { @@ -167,6 +167,18 @@ pub fn select_with_embedder( /// Returns an empty `SelectionOutput` (not an error) when both `catalog_root` is /// absent and `source_dirs` is empty. pub fn select_rich(inputs: &SelectionInputs<'_>) -> Result { + select_rich_with_embedder(inputs, None) +} + +/// Run a rich selection pass using an optional semantic `embedder`. +/// +/// Identical to [`select_rich`] but threads `embedder` into the scorer so the +/// semantic channel is active when one is supplied. Passing `None` reproduces +/// [`select_rich`] exactly, so existing callers are unaffected. +pub fn select_rich_with_embedder( + inputs: &SelectionInputs<'_>, + embedder: Option<&dyn Embedder>, +) -> Result { let index = if let Some(catalog_root) = &inputs.catalog_root { PersonaIndex::from_catalog(catalog_root)? } else { @@ -186,7 +198,7 @@ pub fn select_rich(inputs: &SelectionInputs<'_>) -> Result