diff --git a/.gitignore b/.gitignore index f93f883..b0bebd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ CLAUDE.md .claude utils/save*sh +.fastembed_cache COMMIT_MESSAGE.md .wrks nushell diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..344eaaf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- **Architecture Documentation**: New `docs/*/architecture/` section with ADRs + - ADR-001: stratum-embeddings - Unified embedding library with caching, fallback, + and VectorStore trait (SurrealDB for Kogral, LanceDB for Provisioning/Vapora) + - ADR-002: stratum-llm - Unified LLM provider library with CLI credential detection, + circuit breaker, caching, and Kogral integration +- **Bilingual ADRs**: Full English and Spanish versions of all architecture documents +- **README updates**: Added Stratum Crates section and updated documentation structure + +### Changed + +- Documentation structure now includes `architecture/adrs/` subdirectory in both + language directories (en/es) + +## [0.1.0] - 2026-01-22 + +### Added + +- Initial repository setup +- Main documentation structure (bilingual en/es) +- Branding assets (logos, icons, social variants) +- CI/CD configuration (GitHub Actions, Woodpecker) +- Language guidelines (Rust, Nickel, Nushell, Bash) +- Pre-commit hooks configuration + +[Unreleased]: https://repo.jesusperez.pro/jesus/stratumiops/compare/v0.1.0...HEAD +[0.1.0]: https://repo.jesusperez.pro/jesus/stratumiops/releases/tag/v0.1.0 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..34b22e3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,10658 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "addr" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93b8a41dbe230ad5087cc721f8d41611de654542180586b315d9f4cf6b72bef" +dependencies = [ + "psl-types", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "affinitypool" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dde2a385b82232b559baeec740c37809051c596f9b56e7da0d0da2c8e8f54f6" +dependencies = [ + "async-channel", + "num_cpus", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice 0.2.1", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ammonia" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6" +dependencies = [ + "cssparser", + "html5ever", + "maplit", + "tendril", + "url", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "any_ascii" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c6333e01ba7235575b6ab53e5af10f1c327927fd97c36462917e289557ea64" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "approx" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" +dependencies = [ + "num-traits", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arc-swap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "arrow" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e833808ff2d94ed40d9379848a950d995043c7fb3e81a30b383f4c6033821cc" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad08897b81588f60ba983e3ca39bda2b179bdd84dced378e7df81a5313802ef8" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "num", +] + +[[package]] +name = "arrow-array" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8548ca7c070d8db9ce7aa43f37393e4bfcf3f2d3681df278490772fd1673d08d" +dependencies = [ + "ahash 0.8.12", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "chrono-tz", + "half", + "hashbrown 0.16.1", + "num", +] + +[[package]] +name = "arrow-buffer" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e003216336f70446457e280807a73899dd822feaf02087d31febca1363e2fccc" +dependencies = [ + "bytes", + "half", + "num", +] + +[[package]] +name = "arrow-cast" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919418a0681298d3a77d1a315f625916cb5678ad0d74b9c60108eb15fd083023" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64 0.22.1", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa9bf02705b5cf762b6f764c65f04ae9082c7cfc4e96e0c33548ee3f67012eb" +dependencies = [ + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "regex", +] + +[[package]] +name = "arrow-data" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5c64fff1d142f833d78897a772f2e5b55b36cb3e6320376f0961ab0db7bd6d0" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num", +] + +[[package]] +name = "arrow-ipc" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3594dcddccc7f20fd069bc8e9828ce37220372680ff638c5e00dea427d88f5" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "flatbuffers", + "lz4_flex", + "zstd", +] + +[[package]] +name = "arrow-json" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88cf36502b64a127dc659e3b305f1d993a544eab0d48cce704424e62074dc04b" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "indexmap 2.13.0", + "lexical-core", + "memchr", + "num", + "serde", + "serde_json", + "simdutf8", +] + +[[package]] +name = "arrow-ord" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8f82583eb4f8d84d4ee55fd1cb306720cddead7596edce95b50ee418edf66f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", +] + +[[package]] +name = "arrow-row" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d07ba24522229d9085031df6b94605e0f4b26e099fb7cdeec37abd941a73753" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3aa9e59c611ebc291c28582077ef25c97f1975383f1479b12f3b9ffee2ffabe" +dependencies = [ + "bitflags 2.10.0", + "serde", + "serde_json", +] + +[[package]] +name = "arrow-select" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c41dbbd1e97bfcaee4fcb30e29105fb2c75e4d82ae4de70b792a5d3f66b2e7a" +dependencies = [ + "ahash 0.8.12", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", +] + +[[package]] +name = "arrow-string" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f5183c150fbc619eede22b861ea7c0eebed8eaac0333eaa7f6da5205fd504d" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax", +] + +[[package]] +name = "as-slice" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" +dependencies = [ + "generic-array 0.12.4", + "generic-array 0.13.3", + "generic-array 0.14.7", + "stable_deref_trait", +] + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" +dependencies = [ + "bzip2 0.5.2", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "xz2", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-graphql" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1057a9f7ccf2404d94571dec3451ade1cb524790df6f1ada0d19c2a49f6b0f40" +dependencies = [ + "async-graphql-derive", + "async-graphql-parser", + "async-graphql-value", + "async-io", + "async-trait", + "asynk-strim", + "base64 0.22.1", + "bytes", + "fnv", + "futures-util", + "http 1.4.0", + "indexmap 2.13.0", + "mime", + "multer", + "num-traits", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "static_assertions_next", + "thiserror 2.0.18", +] + +[[package]] +name = "async-graphql-derive" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6cbeadc8515e66450fba0985ce722192e28443697799988265d86304d7cc68" +dependencies = [ + "Inflector", + "async-graphql-parser", + "darling 0.23.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "strum 0.27.2", + "syn 2.0.114", + "thiserror 2.0.18", +] + +[[package]] +name = "async-graphql-parser" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64ef70f77a1c689111e52076da1cd18f91834bcb847de0a9171f83624b07fbf" +dependencies = [ + "async-graphql-value", + "pest", + "serde", + "serde_json", +] + +[[package]] +name = "async-graphql-value" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3ef112905abea9dea592fc868a6873b10ebd3f983e83308f995d6284e9ba41" +dependencies = [ + "bytes", + "indexmap 2.13.0", + "serde", + "serde_json", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.3", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "async_cell" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447ab28afbb345f5408b120702a44e5529ebf90b1796ec76e9528df8e288e6c2" +dependencies = [ + "loom", +] + +[[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 = "asynk-strim" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "aws-config" +version = "1.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.4.0", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959dab27ce613e6c9658eb3621064d0e2027e5f2acb65bc526a43577facea557" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-dynamodb" +version = "1.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df2a8b03419775bfaf4f3ebbb65a9772e9e69eed4467a1b33f22226722340fb" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.92.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d63bd2bdeeb49aa3f9b00c15e18583503b778b2e792fc06284d54e7d5b6566" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.94.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532d93574bf731f311bafb761366f9ece345a0416dbcc273d81d6d1a1205239b" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357e9a029c7524db6a0099cd77fbd5da165540339e7296cca603531bc783b56c" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e523e1c4e8e7e8ff219d732988e22bfeae8a1cafdbe6d9eca1546fa080be7c" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e62db736db19c488966c8d787f52e6270be565727236fd5579eaa301e7bc4a" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.8.1", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1fcbefc7ece1d70dcce29e490f269695dfca2d2bacdeaf9e5c3f799e4e6a42" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5b6167fcdf47399024e81ac08e795180c576a20e4d4ce67949f9a88ae37dc1" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efce7aaaf59ad53c5412f14fc19b2d5c6ab2c3ec688d272fd31f76ec12f44fb0" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65f172bcb02424eb94425db8aed1b6d583b5104d4d5ddddf22402c661a320048" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "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 = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bcrypt" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" +dependencies = [ + "base64 0.22.1", + "blowfish", + "getrandom 0.2.17", + "subtle", + "zeroize", +] + +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitpacking" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a7139abd3d9cebf8cd6f920a389cf3dc9576172e32f4563f188cae3c3eb019" +dependencies = [ + "crunchy", +] + +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "bon" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234655ec178edd82b891e262ea7cf71f6584bcd09eff94db786be23f1821825c" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ec27229c38ed0eb3c0feee3d2c1d6a4379ae44f418a29a658890e062d8f365" +dependencies = [ + "darling 0.23.0", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.114", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cedar-policy" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d91e3b10a0f7f2911774d5e49713c4d25753466f9e11d1cd2ec627f8a2dc857" +dependencies = [ + "cedar-policy-core", + "cedar-policy-validator", + "itertools 0.10.5", + "lalrpop-util", + "ref-cast", + "serde", + "serde_json", + "smol_str", + "thiserror 1.0.69", +] + +[[package]] +name = "cedar-policy-core" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd2315591c6b7e18f8038f0a0529f254235fd902b6c217aabc04f2459b0d9995" +dependencies = [ + "either", + "ipnet", + "itertools 0.10.5", + "lalrpop", + "lalrpop-util", + "lazy_static", + "miette", + "regex", + "rustc_lexer", + "serde", + "serde_json", + "serde_with", + "smol_str", + "stacker", + "thiserror 1.0.69", +] + +[[package]] +name = "cedar-policy-validator" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e756e1b2a5da742ed97e65199ad6d0893e9aa4bd6b34be1de9e70bd1e6adc7df" +dependencies = [ + "cedar-policy-core", + "itertools 0.10.5", + "serde", + "serde_json", + "serde_with", + "smol_str", + "stacker", + "thiserror 1.0.69", + "unicode-security", +] + +[[package]] +name = "census" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf 0.12.1", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "comfy-table" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d05af1e006a2407bedef5af410552494ce5be9090444dbbcb57258c1af3d56" +dependencies = [ + "strum 0.26.3", + "strum_macros 0.26.4", + "unicode-width 0.2.2", +] + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[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" +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 = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array 0.14.7", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "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", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[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.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "dary_heap" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" +dependencies = [ + "serde", +] + +[[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 0.9.12", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "datafusion" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af15bb3c6ffa33011ef579f6b0bcbe7c26584688bd6c994f548e44df67f011a" +dependencies = [ + "arrow", + "arrow-ipc", + "arrow-schema", + "async-trait", + "bytes", + "bzip2 0.6.1", + "chrono", + "datafusion-catalog", + "datafusion-catalog-listing", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-datasource-csv", + "datafusion-datasource-json", + "datafusion-datasource-parquet", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-nested", + "datafusion-functions-table", + "datafusion-functions-window", + "datafusion-optimizer", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-optimizer", + "datafusion-physical-plan", + "datafusion-session", + "datafusion-sql", + "flate2", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot 0.12.5", + "parquet", + "rand 0.9.2", + "regex", + "sqlparser", + "tempfile", + "tokio", + "url", + "uuid", + "xz2", + "zstd", +] + +[[package]] +name = "datafusion-catalog" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187622262ad8f7d16d3be9202b4c1e0116f1c9aa387e5074245538b755261621" +dependencies = [ + "arrow", + "async-trait", + "dashmap 6.1.0", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-plan", + "datafusion-session", + "datafusion-sql", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot 0.12.5", + "tokio", +] + +[[package]] +name = "datafusion-catalog-listing" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9657314f0a32efd0382b9a46fdeb2d233273ece64baa68a7c45f5a192daf0f83" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "log", + "object_store", + "tokio", +] + +[[package]] +name = "datafusion-common" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a83760d9a13122d025fbdb1d5d5aaf93dd9ada5e90ea229add92aa30898b2d1" +dependencies = [ + "ahash 0.8.12", + "arrow", + "arrow-ipc", + "base64 0.22.1", + "chrono", + "half", + "hashbrown 0.14.5", + "indexmap 2.13.0", + "libc", + "log", + "object_store", + "parquet", + "paste", + "recursive", + "sqlparser", + "tokio", + "web-time", +] + +[[package]] +name = "datafusion-common-runtime" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b6234a6c7173fe5db1c6c35c01a12b2aa0f803a3007feee53483218817f8b1e" +dependencies = [ + "futures", + "log", + "tokio", +] + +[[package]] +name = "datafusion-datasource" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7256c9cb27a78709dd42d0c80f0178494637209cac6e29d5c93edd09b6721b86" +dependencies = [ + "arrow", + "async-compression", + "async-trait", + "bytes", + "bzip2 0.6.1", + "chrono", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "flate2", + "futures", + "glob", + "itertools 0.14.0", + "log", + "object_store", + "parquet", + "rand 0.9.2", + "tempfile", + "tokio", + "tokio-util", + "url", + "xz2", + "zstd", +] + +[[package]] +name = "datafusion-datasource-csv" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64533a90f78e1684bfb113d200b540f18f268134622d7c96bbebc91354d04825" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "regex", + "tokio", +] + +[[package]] +name = "datafusion-datasource-json" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7ebeb12c77df0aacad26f21b0d033aeede423a64b2b352f53048a75bf1d6e6" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-session", + "futures", + "object_store", + "serde_json", + "tokio", +] + +[[package]] +name = "datafusion-datasource-parquet" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e783c4c7d7faa1199af2df4761c68530634521b176a8d1331ddbc5a5c75133" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "datafusion-catalog", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-datasource", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate", + "datafusion-physical-expr", + "datafusion-physical-expr-adapter", + "datafusion-physical-expr-common", + "datafusion-physical-optimizer", + "datafusion-physical-plan", + "datafusion-pruning", + "datafusion-session", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot 0.12.5", + "parquet", + "rand 0.9.2", + "tokio", +] + +[[package]] +name = "datafusion-doc" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ee6b1d9a80d13f9deb2291f45c07044b8e62fb540dbde2453a18be17a36429" + +[[package]] +name = "datafusion-execution" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4cec0a57653bec7b933fb248d3ffa3fa3ab3bd33bd140dc917f714ac036f531" +dependencies = [ + "arrow", + "async-trait", + "dashmap 6.1.0", + "datafusion-common", + "datafusion-expr", + "futures", + "log", + "object_store", + "parking_lot 0.12.5", + "rand 0.9.2", + "tempfile", + "url", +] + +[[package]] +name = "datafusion-expr" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef76910bdca909722586389156d0aa4da4020e1631994d50fadd8ad4b1aa05fe" +dependencies = [ + "arrow", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-doc", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr-common", + "indexmap 2.13.0", + "paste", + "recursive", + "serde_json", + "sqlparser", +] + +[[package]] +name = "datafusion-expr-common" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d155ccbda29591ca71a1344dd6bed26c65a4438072b400df9db59447f590bb6" +dependencies = [ + "arrow", + "datafusion-common", + "indexmap 2.13.0", + "itertools 0.14.0", + "paste", +] + +[[package]] +name = "datafusion-functions" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de2782136bd6014670fd84fe3b0ca3b3e4106c96403c3ae05c0598577139977" +dependencies = [ + "arrow", + "arrow-buffer", + "base64 0.22.1", + "blake2", + "blake3", + "chrono", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-macros", + "hex", + "itertools 0.14.0", + "log", + "md-5", + "rand 0.9.2", + "regex", + "sha2", + "unicode-segmentation", + "uuid", +] + +[[package]] +name = "datafusion-functions-aggregate" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07331fc13603a9da97b74fd8a273f4238222943dffdbbed1c4c6f862a30105bf" +dependencies = [ + "ahash 0.8.12", + "arrow", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "half", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-aggregate-common" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5951e572a8610b89968a09b5420515a121fbc305c0258651f318dc07c97ab17" +dependencies = [ + "ahash 0.8.12", + "arrow", + "datafusion-common", + "datafusion-expr-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-functions-nested" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdacca9302c3d8fc03f3e94f338767e786a88a33f5ebad6ffc0e7b50364b9ea3" +dependencies = [ + "arrow", + "arrow-ord", + "datafusion-common", + "datafusion-doc", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-functions-aggregate", + "datafusion-functions-aggregate-common", + "datafusion-macros", + "datafusion-physical-expr-common", + "itertools 0.14.0", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-table" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37ff8a99434fbbad604a7e0669717c58c7c4f14c472d45067c4b016621d981" +dependencies = [ + "arrow", + "async-trait", + "datafusion-catalog", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-plan", + "parking_lot 0.12.5", + "paste", +] + +[[package]] +name = "datafusion-functions-window" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e2aea7c79c926cffabb13dc27309d4eaeb130f4a21c8ba91cdd241c813652b" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-doc", + "datafusion-expr", + "datafusion-functions-window-common", + "datafusion-macros", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "log", + "paste", +] + +[[package]] +name = "datafusion-functions-window-common" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fead257ab5fd2ffc3b40fda64da307e20de0040fe43d49197241d9de82a487f" +dependencies = [ + "datafusion-common", + "datafusion-physical-expr-common", +] + +[[package]] +name = "datafusion-macros" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec6f637bce95efac05cdfb9b6c19579ed4aa5f6b94d951cfa5bb054b7bb4f730" +dependencies = [ + "datafusion-expr", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "datafusion-optimizer" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6583ef666ae000a613a837e69e456681a9faa96347bf3877661e9e89e141d8a" +dependencies = [ + "arrow", + "chrono", + "datafusion-common", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-physical-expr", + "indexmap 2.13.0", + "itertools 0.14.0", + "log", + "recursive", + "regex", + "regex-syntax", +] + +[[package]] +name = "datafusion-physical-expr" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8668103361a272cbbe3a61f72eca60c9b7c706e87cc3565bcf21e2b277b84f6" +dependencies = [ + "ahash 0.8.12", + "arrow", + "datafusion-common", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-functions-aggregate-common", + "datafusion-physical-expr-common", + "half", + "hashbrown 0.14.5", + "indexmap 2.13.0", + "itertools 0.14.0", + "log", + "parking_lot 0.12.5", + "paste", + "petgraph 0.8.3", +] + +[[package]] +name = "datafusion-physical-expr-adapter" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815acced725d30601b397e39958e0e55630e0a10d66ef7769c14ae6597298bb0" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-expr", + "datafusion-functions", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "itertools 0.14.0", +] + +[[package]] +name = "datafusion-physical-expr-common" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6652fe7b5bf87e85ed175f571745305565da2c0b599d98e697bcbedc7baa47c3" +dependencies = [ + "ahash 0.8.12", + "arrow", + "datafusion-common", + "datafusion-expr-common", + "hashbrown 0.14.5", + "itertools 0.14.0", +] + +[[package]] +name = "datafusion-physical-optimizer" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b7d623eb6162a3332b564a0907ba00895c505d101b99af78345f1acf929b5c" +dependencies = [ + "arrow", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "datafusion-pruning", + "itertools 0.14.0", + "log", + "recursive", +] + +[[package]] +name = "datafusion-physical-plan" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2f7f778a1a838dec124efb96eae6144237d546945587557c9e6936b3414558c" +dependencies = [ + "ahash 0.8.12", + "arrow", + "arrow-ord", + "arrow-schema", + "async-trait", + "chrono", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions-aggregate-common", + "datafusion-functions-window-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "futures", + "half", + "hashbrown 0.14.5", + "indexmap 2.13.0", + "itertools 0.14.0", + "log", + "parking_lot 0.12.5", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "datafusion-pruning" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1e59e2ca14fe3c30f141600b10ad8815e2856caa59ebbd0e3e07cd3d127a65" +dependencies = [ + "arrow", + "arrow-schema", + "datafusion-common", + "datafusion-datasource", + "datafusion-expr-common", + "datafusion-physical-expr", + "datafusion-physical-expr-common", + "datafusion-physical-plan", + "itertools 0.14.0", + "log", +] + +[[package]] +name = "datafusion-session" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ef8e2745583619bd7a49474e8f45fbe98ebb31a133f27802217125a7b3d58d" +dependencies = [ + "arrow", + "async-trait", + "dashmap 6.1.0", + "datafusion-common", + "datafusion-common-runtime", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-physical-plan", + "datafusion-sql", + "futures", + "itertools 0.14.0", + "log", + "object_store", + "parking_lot 0.12.5", + "tokio", +] + +[[package]] +name = "datafusion-sql" +version = "50.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89abd9868770386fede29e5a4b14f49c0bf48d652c3b9d7a8a0332329b87d50b" +dependencies = [ + "arrow", + "bigdecimal", + "datafusion-common", + "datafusion-expr", + "indexmap 2.13.0", + "log", + "recursive", + "regex", + "sqlparser", +] + +[[package]] +name = "deepsize" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb987ec36f6bf7bfbea3f928b75590b736fc42af8e54d97592481351b2b96c" +dependencies = [ + "deepsize_derive", +] + +[[package]] +name = "deepsize_derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990101d41f3bc8c1a45641024377ee284ecc338e5ecf3ea0f0e236d897c72796" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "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.114", +] + +[[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.114", +] + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[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-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[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 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dmp" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2dfc7a18dffd3ef60a442b72a827126f1557d914620f8fc4d1049916da43c1" +dependencies = [ + "trice", + "urlencoding", +] + +[[package]] +name = "double-ended-peekable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57" + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "earcutr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" +dependencies = [ + "itertools 0.11.0", + "num-traits", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[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 = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "ext-sort" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5d3b056bcc471d38082b8c453acb6670f7327fd44219b3c411e40834883569" +dependencies = [ + "log", + "rayon", + "rmp-serde", + "serde", + "tempfile", +] + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "fastdivide" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" + +[[package]] +name = "fastembed" +version = "5.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a3f841f27a44bcc32214f8df75cc9b6cea55dbbebbfe546735690eab5bb2d2" +dependencies = [ + "anyhow", + "hf-hub", + "image", + "ndarray 0.17.2", + "ort", + "safetensors", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" +dependencies = [ + "bitflags 2.10.0", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "fs4" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" +dependencies = [ + "rustix 0.38.44", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsst" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffdff7a2d68d22afc0657eddde3e946371ce7cfe730a3f78a5ed44ea5b1cb2e" +dependencies = [ + "arrow-array", + "rand 0.9.2", +] + +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" +dependencies = [ + "utf8-ranges", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result 0.4.1", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" +dependencies = [ + "typenum", +] + +[[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 = "geo" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f811f663912a69249fa620dcd2a005db7254529da2d8a0b23942e81f47084501" +dependencies = [ + "earcutr", + "float_next_after", + "geo-types", + "geographiclib-rs", + "log", + "num-traits", + "robust", + "rstar 0.12.2", + "serde", + "spade", +] + +[[package]] +name = "geo" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc1a1678e54befc9b4bcab6cd43b8e7f834ae8ea121118b0fd8c42747675b4a" +dependencies = [ + "earcutr", + "float_next_after", + "geo-types", + "geographiclib-rs", + "i_overlay", + "log", + "num-traits", + "robust", + "rstar 0.12.2", + "spade", +] + +[[package]] +name = "geo-traits" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7c353d12a704ccfab1ba8bfb1a7fe6cb18b665bf89d37f4f7890edcd260206" +dependencies = [ + "geo-types", +] + +[[package]] +name = "geo-types" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f8647af4005fa11da47cd56252c6ef030be8fa97bdbf355e7dfb6348f0a82c" +dependencies = [ + "approx 0.5.1", + "num-traits", + "rayon", + "rstar 0.10.0", + "rstar 0.11.0", + "rstar 0.12.2", + "rstar 0.8.4", + "rstar 0.9.3", + "serde", +] + +[[package]] +name = "geoarrow-array" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d1884b17253d8572e88833c282fcbb442365e4ae5f9052ced2831608253436c" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-schema", + "geo-traits", + "geoarrow-schema", + "num-traits", + "wkb", + "wkt", +] + +[[package]] +name = "geoarrow-expr-geo" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67d3b543bc3ebeffdc204b67d69b8f9fcd33d76269ddd4a4618df99f053a934" +dependencies = [ + "arrow-array", + "arrow-buffer", + "geo 0.31.0", + "geo-traits", + "geoarrow-array", + "geoarrow-schema", +] + +[[package]] +name = "geoarrow-schema" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02f1b18b1c9a44ecd72be02e53d6e63bbccfdc8d1765206226af227327e2be6e" +dependencies = [ + "arrow-schema", + "geo-traits", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "geodatafusion" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d676b8d8b5f391ab4270ba31e9b599ee2c3d780405a38e272a0a7565ea189c" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-schema", + "datafusion", + "geo 0.31.0", + "geo-traits", + "geoarrow-array", + "geoarrow-expr-geo", + "geoarrow-schema", + "geohash", + "thiserror 1.0.69", + "wkt", +] + +[[package]] +name = "geographiclib-rs" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841" +dependencies = [ + "libm", +] + +[[package]] +name = "geohash" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fb94b1a65401d6cbf22958a9040aa364812c26674f841bee538b12c135db1e6" +dependencies = [ + "geo-types", + "libm", +] + +[[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", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[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 = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", +] + +[[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", + "serde", + "serde_core", +] + +[[package]] +name = "heapless" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634bd4d29cbf24424d0a4bfcbf80c6960129dc24424752a7d1d1390607023422" +dependencies = [ + "as-slice 0.1.5", + "generic-array 0.14.7", + "hash32 0.1.1", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hf-hub" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" +dependencies = [ + "dirs", + "http 1.4.0", + "indicatif", + "libc", + "log", + "native-tls", + "rand 0.9.2", + "reqwest 0.12.28", + "serde", + "serde_json", + "thiserror 2.0.18", + "ureq 2.12.1", + "windows-sys 0.60.2", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha256" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f0ae375a85536cac3a243e3a9cda80a47910348abdea7e2c22f8ec556d586d" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +dependencies = [ + "log", + "markup5ever", + "match_token", +] + +[[package]] +name = "htmlescape" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "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 = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.5", +] + +[[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 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "hyperloglogplus" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "621debdf94dcac33e50475fdd76d34d5ea9c0362a834b9db08c3024696c1fbe3" +dependencies = [ + "serde", +] + +[[package]] +name = "i_float" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010025c2c532c8d82e42d0b8bb5184afa449fa6f06c709ea9adcb16c49ae405b" +dependencies = [ + "libm", +] + +[[package]] +name = "i_key_sort" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9190f86706ca38ac8add223b2aed8b1330002b5cdbbce28fb58b10914d38fc27" + +[[package]] +name = "i_overlay" +version = "4.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcccbd4e4274e0f80697f5fbc6540fdac533cce02f2081b328e68629cce24f9" +dependencies = [ + "i_float", + "i_key_sort", + "i_shape", + "i_tree", + "rayon", +] + +[[package]] +name = "i_shape" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea154b742f7d43dae2897fcd5ead86bc7b5eefcedd305a7ebf9f69d44d61082" +dependencies = [ + "i_float", +] + +[[package]] +name = "i_tree" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e6d558e6d4c7b82bc51d9c771e7a927862a161a7d87bf2b0541450e0e20915" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +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.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core 0.5.1", + "zune-jpeg 0.5.11", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width 0.2.2", + "web-time", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array 0.14.7", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[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", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonb" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a901f06163d352fbe41c3c2ff5e08b75330a003cc941e988fb501022f5421e6" +dependencies = [ + "byteorder", + "ethnum", + "fast-float2", + "itoa", + "jiff", + "nom 8.0.0", + "num-traits", + "ordered-float 5.1.0", + "rand 0.9.2", + "ryu", + "serde", + "serde_json", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph 0.6.5", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "lance" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c439decbc304e180748e34bb6d3df729069a222e83e74e2185c38f107136e9" +dependencies = [ + "arrow", + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-ipc", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "async-recursion", + "async-trait", + "async_cell", + "aws-credential-types", + "aws-sdk-dynamodb", + "byteorder", + "bytes", + "chrono", + "dashmap 6.1.0", + "datafusion", + "datafusion-expr", + "datafusion-functions", + "datafusion-physical-expr", + "datafusion-physical-plan", + "deepsize", + "either", + "futures", + "half", + "humantime", + "itertools 0.13.0", + "lance-arrow", + "lance-core", + "lance-datafusion", + "lance-encoding", + "lance-file", + "lance-geo", + "lance-index", + "lance-io", + "lance-linalg", + "lance-namespace", + "lance-table", + "log", + "moka", + "object_store", + "permutation", + "pin-project", + "prost", + "prost-types", + "rand 0.9.2", + "roaring", + "semver", + "serde", + "serde_json", + "snafu", + "tantivy", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "lance-arrow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ee5508b225456d3d56998eaeef0d8fbce5ea93856df47b12a94d2e74153210" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "arrow-select", + "bytes", + "getrandom 0.2.17", + "half", + "jsonb", + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "lance-bitpacking" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c065fb3bd4a8cc4f78428443e990d4921aa08f707b676753db740e0b402a21" +dependencies = [ + "arrayref", + "paste", + "seq-macro", +] + +[[package]] +name = "lance-core" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8856abad92e624b75cd57a04703f6441948a239463bdf973f2ac1924b0bcdbe" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-schema", + "async-trait", + "byteorder", + "bytes", + "chrono", + "datafusion-common", + "datafusion-sql", + "deepsize", + "futures", + "lance-arrow", + "libc", + "log", + "mock_instant", + "moka", + "num_cpus", + "object_store", + "pin-project", + "prost", + "rand 0.9.2", + "roaring", + "serde_json", + "snafu", + "tempfile", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "lance-datafusion" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8835308044cef5467d7751be87fcbefc2db01c22370726a8704bd62991693f" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ord", + "arrow-schema", + "arrow-select", + "async-trait", + "chrono", + "datafusion", + "datafusion-common", + "datafusion-functions", + "datafusion-physical-expr", + "futures", + "jsonb", + "lance-arrow", + "lance-core", + "lance-datagen", + "lance-geo", + "log", + "pin-project", + "prost", + "snafu", + "tokio", + "tracing", +] + +[[package]] +name = "lance-datagen" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612de1e888bb36f6bf51196a6eb9574587fdf256b1759a4c50e643e00d5f96d0" +dependencies = [ + "arrow", + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "futures", + "half", + "hex", + "rand 0.9.2", + "rand_xoshiro", + "random_word", +] + +[[package]] +name = "lance-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b456b29b135d3c7192602e516ccade38b5483986e121895fa43cf1fdb38bf60" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "arrow-select", + "bytemuck", + "byteorder", + "bytes", + "fsst", + "futures", + "hex", + "hyperloglogplus", + "itertools 0.13.0", + "lance-arrow", + "lance-bitpacking", + "lance-core", + "log", + "lz4", + "num-traits", + "prost", + "prost-build", + "prost-types", + "rand 0.9.2", + "snafu", + "strum 0.26.3", + "tokio", + "tracing", + "xxhash-rust", + "zstd", +] + +[[package]] +name = "lance-file" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab1538d14d5bb3735b4222b3f5aff83cfa59cc6ef7cdd3dd9139e4c77193c80b" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "async-recursion", + "async-trait", + "byteorder", + "bytes", + "datafusion-common", + "deepsize", + "futures", + "lance-arrow", + "lance-core", + "lance-encoding", + "lance-io", + "log", + "num-traits", + "object_store", + "prost", + "prost-build", + "prost-types", + "snafu", + "tokio", + "tracing", +] + +[[package]] +name = "lance-geo" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a69a2f3b55703d9c240ad7c5ffa2c755db69e9cf8aa05efe274a212910472d" +dependencies = [ + "datafusion", + "geo-types", + "geoarrow-array", + "geoarrow-schema", + "geodatafusion", +] + +[[package]] +name = "lance-index" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea84613df6fa6b9168a1f056ba4f9cb73b90a1b452814c6fd4b3529bcdbfc78" +dependencies = [ + "arrow", + "arrow-arith", + "arrow-array", + "arrow-ord", + "arrow-schema", + "arrow-select", + "async-channel", + "async-recursion", + "async-trait", + "bitpacking", + "bitvec", + "bytes", + "crossbeam-queue", + "datafusion", + "datafusion-common", + "datafusion-expr", + "datafusion-physical-expr", + "datafusion-sql", + "deepsize", + "dirs", + "fst", + "futures", + "half", + "itertools 0.13.0", + "jsonb", + "lance-arrow", + "lance-core", + "lance-datafusion", + "lance-datagen", + "lance-encoding", + "lance-file", + "lance-io", + "lance-linalg", + "lance-table", + "libm", + "log", + "ndarray 0.16.1", + "num-traits", + "object_store", + "prost", + "prost-build", + "prost-types", + "rand 0.9.2", + "rand_distr 0.5.1", + "rayon", + "roaring", + "serde", + "serde_json", + "snafu", + "tantivy", + "tempfile", + "tokio", + "tracing", + "twox-hash", + "uuid", +] + +[[package]] +name = "lance-io" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3fc4c1d941fceef40a0edbd664dbef108acfc5d559bb9e7f588d0c733cbc35" +dependencies = [ + "arrow", + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "arrow-select", + "async-recursion", + "async-trait", + "aws-config", + "aws-credential-types", + "byteorder", + "bytes", + "chrono", + "deepsize", + "futures", + "lance-arrow", + "lance-core", + "lance-namespace", + "log", + "object_store", + "object_store_opendal", + "opendal", + "path_abs", + "pin-project", + "prost", + "rand 0.9.2", + "serde", + "shellexpand", + "snafu", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "lance-linalg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ffbc5ce367fbf700a69de3fe0612ee1a11191a64a632888610b6bacfa0f63" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-schema", + "cc", + "deepsize", + "half", + "lance-arrow", + "lance-core", + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "lance-namespace" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791bbcd868ee758123a34e07d320a1fb99379432b5ecc0e78d6b4686e999b629" +dependencies = [ + "arrow", + "async-trait", + "bytes", + "lance-core", + "lance-namespace-reqwest-client", + "snafu", +] + +[[package]] +name = "lance-namespace-impls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee713505576f6b1988a491f77c7ca8b0cf7090a393598e63c85079fa70a53ebf" +dependencies = [ + "arrow", + "arrow-ipc", + "arrow-schema", + "async-trait", + "bytes", + "futures", + "lance", + "lance-core", + "lance-index", + "lance-io", + "lance-namespace", + "log", + "object_store", + "rand 0.9.2", + "serde_json", + "snafu", + "tokio", + "url", +] + +[[package]] +name = "lance-namespace-reqwest-client" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea349999bcda4eea53fc05d334b3775ec314761e6a706555c777d7a29b18d19" +dependencies = [ + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "lance-table" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fdb2d56bfa4d1511c765fa0cc00fdaa37e5d2d1cd2f57b3c6355d9072177052" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-ipc", + "arrow-schema", + "async-trait", + "aws-credential-types", + "aws-sdk-dynamodb", + "byteorder", + "bytes", + "chrono", + "deepsize", + "futures", + "lance-arrow", + "lance-core", + "lance-file", + "lance-io", + "log", + "object_store", + "prost", + "prost-build", + "prost-types", + "rand 0.9.2", + "rangemap", + "roaring", + "semver", + "serde", + "serde_json", + "snafu", + "tokio", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "lance-testing" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ccb1a4a9284435c6a8c02c8c06e7e041bece0d7f722152159353cf55dc51e3" +dependencies = [ + "arrow-array", + "arrow-schema", + "lance-arrow", + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "lancedb" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9217d7d3a1f4e088bdedaad9b4fa79045b077e07f961f1cd3ec6f90850c425f2" +dependencies = [ + "ahash 0.8.12", + "arrow", + "arrow-array", + "arrow-cast", + "arrow-data", + "arrow-ipc", + "arrow-ord", + "arrow-schema", + "arrow-select", + "async-trait", + "bytes", + "chrono", + "datafusion", + "datafusion-catalog", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-physical-plan", + "futures", + "half", + "lance", + "lance-arrow", + "lance-core", + "lance-datafusion", + "lance-datagen", + "lance-encoding", + "lance-file", + "lance-index", + "lance-io", + "lance-linalg", + "lance-namespace", + "lance-namespace-impls", + "lance-table", + "lance-testing", + "lazy_static", + "log", + "moka", + "num-traits", + "object_store", + "pin-project", + "rand 0.9.2", + "regex", + "semver", + "serde", + "serde_json", + "serde_with", + "snafu", + "tempfile", + "tokio", + "url", + "uuid", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "levenshtein_automata" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexicmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7378d131ddf24063b32cbd7e91668d183140c4b3906270635a4d633d1068ea5d" +dependencies = [ + "any_ascii", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "linfa-linalg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e7562b41c8876d3367897067013bb2884cc78e6893f092ecd26b305176ac82" +dependencies = [ + "ndarray 0.15.6", + "num-traits", + "rand 0.8.5", + "thiserror 1.0.69", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[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 = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[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-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "lzma-rust2" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[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 = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "match_token" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "num_cpus", + "once_cell", + "rawpointer", + "thread-tree", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "measure_time" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e" +dependencies = [ + "log", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror 1.0.69", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mock_instant" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" + +[[package]] +name = "moka" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot 0.12.5", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[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.114", +] + +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 1.4.0", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "murmurhash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" + +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe 0.1.6", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "approx 0.4.0", + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "ndarray-stats" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af5a8477ac96877b5bd1fd67e0c28736c12943aba24eda92b127e036b0c8f400" +dependencies = [ + "indexmap 1.9.3", + "itertools 0.10.5", + "ndarray 0.15.6", + "noisy_float", + "num-integer", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "noisy_float" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c16843be85dd410c6a12251c4eca0dd1d3ee8c5725f746c4d5e0fdcec0a864b2" +dependencies = [ + "num-traits", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "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", + "libm", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfbfff40aeccab00ec8a910b57ca8ecf4319b335c542f2edcd19dd25a1e2a00" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "form_urlencoded", + "futures", + "http 1.4.0", + "http-body-util", + "httparse", + "humantime", + "hyper 1.8.1", + "itertools 0.14.0", + "md-5", + "parking_lot 0.12.5", + "percent-encoding", + "quick-xml 0.38.4", + "rand 0.9.2", + "reqwest 0.12.28", + "ring", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "object_store_opendal" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113ab0769e972eee585e57407b98de08bda5354fa28e8ba4d89038d6cb6a8991" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures", + "object_store", + "opendal", + "pin-project", + "tokio", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oneshot" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" + +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +dependencies = [ + "bitflags 2.10.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "opendal" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d075ab8a203a6ab4bc1bce0a4b9fe486a72bf8b939037f4b78d95386384bc80a" +dependencies = [ + "anyhow", + "backon", + "base64 0.22.1", + "bytes", + "crc32c", + "futures", + "getrandom 0.2.17", + "http 1.4.0", + "http-body 1.0.1", + "jiff", + "log", + "md-5", + "percent-encoding", + "quick-xml 0.38.4", + "reqsign", + "reqwest 0.12.28", + "serde", + "serde_json", + "sha2", + "tokio", + "url", + "uuid", +] + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "ort" +version = "2.0.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5df903c0d2c07b56950f1058104ab0c8557159f2741782223704de9be73c3c" +dependencies = [ + "ndarray 0.17.2", + "ort-sys", + "smallvec", + "tracing", + "ureq 3.1.4", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b" +dependencies = [ + "hmac-sha256", + "lzma-rust2", + "ureq 3.1.4", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "ownedbytes" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbd56f7631767e61784dc43f8580f403f4475bd4aaa4da003e6295e1bab4a7e" +dependencies = [ + "stable_deref_trait", +] + +[[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.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[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 0.9.12", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[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 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "parquet" +version = "56.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dbd48ad52d7dccf8ea1b90a3ddbfaea4f69878dd7683e51c507d4bc52b5b27" +dependencies = [ + "ahash 0.8.12", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-ipc", + "arrow-schema", + "arrow-select", + "base64 0.22.1", + "brotli", + "bytes", + "chrono", + "flate2", + "futures", + "half", + "hashbrown 0.16.1", + "lz4_flex", + "num", + "num-bigint", + "object_store", + "paste", + "ring", + "seq-macro", + "simdutf8", + "snap", + "thrift", + "tokio", + "twox-hash", + "zstd", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + +[[package]] +name = "path_abs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ef02f6342ac01d8a93b65f96db53fe68a92a15f41144f97fb00a9e669633c3" +dependencies = [ + "serde", + "serde_derive", + "std_prelude", + "stfu8", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pdqselect" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec91767ecc0a0bbe558ce8c9da33c068066c57ecc8bb8477ef8c1ad3ef77c27" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "permutation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df202b0b0f5b8e389955afd5f27b007b00fb948162953f1db9c70d2c7e3157d7" + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset 0.4.2", + "indexmap 2.13.0", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", + "indexmap 2.13.0", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "serde", +] + +[[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 = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared 0.12.1", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.114", + "unicase", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", + "unicase", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.6.4", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.10.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.5", + "protobuf", + "thiserror 2.0.18", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph 0.7.1", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.114", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "psm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick_cache" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55a1aa7668676bb93926cd4e9cdfe60f03bb866553bcca9112554911b6d3dc" +dependencies = [ + "ahash 0.8.12", + "equivalent", + "hashbrown 0.14.5", + "parking_lot 0.12.5", +] + +[[package]] +name = "quick_cache" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" +dependencies = [ + "ahash 0.8.12", + "equivalent", + "hashbrown 0.16.1", + "parking_lot 0.12.5", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.36", + "socket2 0.6.2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.36", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +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 = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", + "serde", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[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_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[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.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[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.2", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "random_word" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47a395bdb55442b883c89062d6bcff25dc90fa5f8369af81e0ac6d49d78cf81" +dependencies = [ + "ahash 0.8.12", + "brotli", + "paste", + "rand 0.9.2", + "unicase", +] + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.2", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +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 0.14.0", + "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 = "reblessive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc4a4ea2a66a41a1152c4b3d86e8954dc087bdf33af35446e6e176db4e73c8c" + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqsign" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43451dbf3590a7590684c25fb8d12ecdcc90ed3ac123433e500447c7d77ed701" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "chrono", + "form_urlencoded", + "getrandom 0.2.17", + "hex", + "hmac", + "home", + "http 1.4.0", + "jsonwebtoken", + "log", + "once_cell", + "percent-encoding", + "quick-xml 0.37.5", + "rand 0.8.5", + "reqwest 0.12.28", + "rsa", + "rust-ini", + "serde", + "serde_json", + "sha1", + "sha2", + "tokio", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 1.0.5", +] + +[[package]] +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.36", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "revision" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f53179a035f881adad8c4d58a2c599c6b4a8325b989c68d178d7a34d1b1e4c" +dependencies = [ + "revision-derive 0.10.0", +] + +[[package]] +name = "revision" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b8ee532f15b2f0811eb1a50adf10d036e14a6cdae8d99893e7f3b921cb227d" +dependencies = [ + "chrono", + "geo 0.28.0", + "regex", + "revision-derive 0.11.0", + "roaring", + "rust_decimal", + "uuid", +] + +[[package]] +name = "revision-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0ec466e5d8dca9965eb6871879677bef5590cf7525ad96cae14376efb75073" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "revision-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3415e1bc838c36f9a0a2ac60c0fa0851c72297685e66592c44870d82834dfa2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[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 = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rmpv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" +dependencies = [ + "rmp", +] + +[[package]] +name = "roaring" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41589aba99537475bf697f2118357cad1c31590c5a1b9f6d9fc4ad6d07503661" +dependencies = [ + "bytemuck", + "byteorder", + "serde", +] + +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rstar" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a45c0e8804d37e4d97e55c6f258bc9ad9c5ee7b07437009dd152d764949a27c" +dependencies = [ + "heapless 0.6.1", + "num-traits", + "pdqselect", + "serde", + "smallvec", +] + +[[package]] +name = "rstar" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40f1bfe5acdab44bc63e6699c28b74f75ec43afb59f3eda01e145aff86a25fa" +dependencies = [ + "heapless 0.7.17", + "num-traits", + "serde", + "smallvec", +] + +[[package]] +name = "rstar" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f39465655a1e3d8ae79c6d9e007f4953bfc5d55297602df9dc38f9ae9f1359a" +dependencies = [ + "heapless 0.7.17", + "num-traits", + "serde", + "smallvec", +] + +[[package]] +name = "rstar" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73111312eb7a2287d229f06c00ff35b51ddee180f017ab6dec1f69d62ac098d6" +dependencies = [ + "heapless 0.7.17", + "num-traits", + "serde", + "smallvec", +] + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless 0.8.0", + "num-traits", + "serde", + "smallvec", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rust-stemmers" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "rust_decimal" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_lexer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86aae0c77166108c01305ee1a36a1e77289d7dc6ca0a3cd91ff4992de2d16a5" +dependencies = [ + "unicode-xid", +] + +[[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 = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.9", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "safetensors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5" +dependencies = [ + "hashbrown 0.16.1", + "serde", + "serde_json", +] + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[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 = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-content" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[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 = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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 = "shellexpand" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +dependencies = [ + "dirs", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.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 = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" +dependencies = [ + "serde", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.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 = "spade" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" +dependencies = [ + "hashbrown 0.15.5", + "num-traits", + "robust", + "smallvec", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +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", +] + +[[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 7.1.3", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "sqlparser" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4b661c54b1e4b603b37873a18c59920e4c51ea8ea2cf527d925424dbd4437c" +dependencies = [ + "log", + "recursive", + "sqlparser_derive", +] + +[[package]] +name = "sqlparser_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "static_assertions_next" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" + +[[package]] +name = "std_prelude" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" + +[[package]] +name = "stfu8" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978" + +[[package]] +name = "storekey" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c42833834a5d23b344f71d87114e0cc9994766a5c42938f4b50e7b2aef85b2" +dependencies = [ + "byteorder", + "memchr", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "stratum-embeddings" +version = "0.1.0" +dependencies = [ + "approx 0.5.1", + "arrow", + "async-trait", + "fastembed", + "futures", + "humantime-serde", + "lancedb", + "moka", + "prometheus", + "reqwest 0.13.1", + "serde", + "serde_json", + "sled", + "surrealdb", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-test", + "tracing", + "tracing-subscriber", + "xxhash-rust", +] + +[[package]] +name = "stratum-llm" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "dirs", + "futures", + "moka", + "prometheus", + "reqwest 0.13.1", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.18", + "tokio", + "tokio-test", + "tracing", + "uuid", + "which", + "xxhash-rust", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot 0.12.5", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[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.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", +] + +[[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.114", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "surrealdb" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2679904b834ec4481a5305c5ac5ab68c0b6c8e3538e744c40b5a9b9867d3f5fa" +dependencies = [ + "arrayvec", + "async-channel", + "bincode", + "chrono", + "dmp", + "futures", + "geo 0.28.0", + "getrandom 0.3.4", + "indexmap 2.13.0", + "path-clean", + "pharos", + "reblessive", + "reqwest 0.12.28", + "revision 0.11.0", + "ring", + "rust_decimal", + "rustls 0.23.36", + "rustls-pki-types", + "semver", + "serde", + "serde-content", + "serde_json", + "surrealdb-core", + "thiserror 1.0.69", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tracing", + "trice", + "url", + "uuid", + "wasm-bindgen-futures", + "wasmtimer", + "ws_stream_wasm", +] + +[[package]] +name = "surrealdb-core" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e764f99f016ddfaedafc9c777a830b371a1b2e536b5d146a1ef69674e041ed6" +dependencies = [ + "addr", + "affinitypool", + "ahash 0.8.12", + "ammonia", + "any_ascii", + "argon2", + "async-channel", + "async-executor", + "async-graphql", + "base64 0.21.7", + "bcrypt", + "bincode", + "blake3", + "bytes", + "castaway", + "cedar-policy", + "chrono", + "ciborium", + "dashmap 5.5.3", + "deunicode", + "dmp", + "ext-sort", + "fst", + "futures", + "fuzzy-matcher", + "geo 0.28.0", + "geo-types", + "getrandom 0.3.4", + "hex", + "http 1.4.0", + "ipnet", + "jsonwebtoken", + "lexicmp", + "linfa-linalg", + "md-5", + "nanoid", + "ndarray 0.15.6", + "ndarray-stats", + "num-traits", + "num_cpus", + "object_store", + "parking_lot 0.12.5", + "pbkdf2", + "pharos", + "phf 0.11.3", + "pin-project-lite", + "quick_cache 0.5.2", + "radix_trie", + "rand 0.8.5", + "rayon", + "reblessive", + "regex", + "revision 0.11.0", + "ring", + "rmpv", + "roaring", + "rust-stemmers", + "rust_decimal", + "scrypt", + "semver", + "serde", + "serde-content", + "serde_json", + "sha1", + "sha2", + "snap", + "storekey", + "strsim", + "subtle", + "surrealkv", + "sysinfo", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tracing", + "trice", + "ulid", + "unicase", + "url", + "uuid", + "vart 0.8.1", + "wasm-bindgen-futures", + "wasmtimer", + "ws_stream_wasm", +] + +[[package]] +name = "surrealkv" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a5041979bdff8599a1d5f6cb7365acb9a79664e2a84e5c4fddac2b3969f7d1" +dependencies = [ + "ahash 0.8.12", + "bytes", + "chrono", + "crc32fast", + "double-ended-peekable", + "getrandom 0.2.17", + "lru", + "parking_lot 0.12.5", + "quick_cache 0.6.18", + "revision 0.10.0", + "vart 0.9.3", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +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.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tantivy" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a966cb0e76e311f09cf18507c9af192f15d34886ee43d7ba7c7e3803660c43" +dependencies = [ + "aho-corasick", + "arc-swap", + "base64 0.22.1", + "bitpacking", + "bon", + "byteorder", + "census", + "crc32fast", + "crossbeam-channel", + "downcast-rs", + "fastdivide", + "fnv", + "fs4", + "htmlescape", + "hyperloglogplus", + "itertools 0.14.0", + "levenshtein_automata", + "log", + "lru", + "lz4_flex", + "measure_time", + "memmap2", + "once_cell", + "oneshot", + "rayon", + "regex", + "rust-stemmers", + "rustc-hash", + "serde", + "serde_json", + "sketches-ddsketch", + "smallvec", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "thiserror 2.0.18", + "time", + "uuid", + "winapi", +] + +[[package]] +name = "tantivy-bitpacker" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adc286a39e089ae9938935cd488d7d34f14502544a36607effd2239ff0e2494" +dependencies = [ + "bitpacking", +] + +[[package]] +name = "tantivy-columnar" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6300428e0c104c4f7db6f95b466a6f5c1b9aece094ec57cdd365337908dc7344" +dependencies = [ + "downcast-rs", + "fastdivide", + "itertools 0.14.0", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", +] + +[[package]] +name = "tantivy-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b6ea6090ce03dc72c27d0619e77185d26cc3b20775966c346c6d4f7e99d7f" +dependencies = [ + "async-trait", + "byteorder", + "ownedbytes", + "serde", + "time", +] + +[[package]] +name = "tantivy-fst" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" +dependencies = [ + "byteorder", + "regex-syntax", + "utf8-ranges", +] + +[[package]] +name = "tantivy-query-grammar" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e810cdeeebca57fc3f7bfec5f85fdbea9031b2ac9b990eb5ff49b371d52bbe6a" +dependencies = [ + "nom 7.1.3", + "serde", + "serde_json", +] + +[[package]] +name = "tantivy-sstable" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709f22c08a4c90e1b36711c1c6cad5ae21b20b093e535b69b18783dd2cb99416" +dependencies = [ + "futures-util", + "itertools 0.14.0", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-fst", + "zstd", +] + +[[package]] +name = "tantivy-stacker" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bcdebb267671311d1e8891fd9d1301803fdb8ad21ba22e0a30d0cab49ba59c1" +dependencies = [ + "murmurhash32", + "rand_distr 0.4.3", + "tantivy-common", +] + +[[package]] +name = "tantivy-tokenizer-api" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa942fcee81e213e09715bbce8734ae2180070b97b33839a795ba1de201547d" +dependencies = [ + "serde", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[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.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thread-tree" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbd370cb847953a25954d9f63e14824a36113f8c72eecf6eccef5dc4b45d630" +dependencies = [ + "crossbeam-channel", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "thrift" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" +dependencies = [ + "byteorder", + "integer-encoding", + "ordered-float 2.10.1", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223" +dependencies = [ + "ahash 0.8.12", + "aho-corasick", + "compact_str", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.4", + "itertools 0.14.0", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.2", + "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.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot 0.12.5", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.36", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" +dependencies = [ + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.36", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +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", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "bitflags 2.10.0", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "iri-string", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", +] + +[[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 = [ + "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.114", +] + +[[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.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trice" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3aaab10ae9fac0b10f392752bf56f0fd20845f39037fec931e8537b105b515a" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.23.36", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +dependencies = [ + "rand 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.2", + "serde", + "web-time", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +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-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-security" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4ddba1535dd35ed8b61c52166b7155d7f4e4b8847cec6f48e71dc66d8b5e50" +dependencies = [ + "unicode-normalization", + "unicode-script", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "native-tls", + "once_cell", + "rustls 0.23.36", + "rustls-pki-types", + "serde", + "serde_json", + "socks", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "ureq" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +dependencies = [ + "base64 0.22.1", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls-pki-types", + "socks", + "ureq-proto", + "utf-8", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64 0.22.1", + "http 1.4.0", + "httparse", + "log", +] + +[[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", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vart" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87782b74f898179396e93c0efabb38de0d58d50bbd47eae00c71b3a1144dbbae" + +[[package]] +name = "vart" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1982d899e57d646498709735f16e9224cf1e8680676ad687f930cf8b5b555ae" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +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.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmtimer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7ed9d8b15c7fb594d72bfb4b5a276f3d2029333cd93a932f376f5937f6f80ee" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.12.5", + "pin-utils", + "wasm-bindgen", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +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 = "web_atoms" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" +dependencies = [ + "phf 0.11.3", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.5", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix 1.1.3", + "winsafe", +] + +[[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-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-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[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.114", +] + +[[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.114", +] + +[[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.114", +] + +[[package]] +name = "windows-link" +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 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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +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 0.52.6", + "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-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[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_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[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_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[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 = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "wkb" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a120b336c7ad17749026d50427c23d838ecb50cd64aaea6254b5030152f890a9" +dependencies = [ + "byteorder", + "geo-traits", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "wkt" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb2b923ccc882312e559ffaa832a055ba9d1ac0cc8e86b3e25453247e4b81d7" +dependencies = [ + "geo-traits", + "geo-types", + "log", + "num-traits", + "thiserror 1.0.69", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[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.18", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2959ca473aae96a14ecedf501d20b3608d2825ba280d5adb57d651721885b0c2" +dependencies = [ + "zune-core 0.5.1", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f72e94e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,60 @@ +[workspace] +members = ["crates/*"] +resolver = "2" + +[workspace.package] +edition = "2021" +license = "MIT OR Apache-2.0" + +[workspace.dependencies] +# Async runtime +tokio = { version = "1.49", features = ["full"] } +async-trait = "0.1" +futures = "0.3" + +# HTTP client +reqwest = { version = "0.13", features = ["json"] } + +# Serialization +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" +humantime-serde = "1.1" + +# Caching +moka = { version = "0.12", features = ["future"] } +sled = "0.34" + +# Embeddings +fastembed = "5.8" + +# Vector storage +lancedb = "0.23" +surrealdb = { version = "2.5", features = ["kv-mem"] } +# LOCKED: Arrow 56.x required for LanceDB 0.23 compatibility +# LanceDB 0.23 uses Arrow 56.2.0 internally - Arrow 57 breaks API compatibility +# DO NOT upgrade to Arrow 57 until LanceDB supports it +arrow = "=56" + +# Error handling +thiserror = "2.0" +anyhow = "1.0" + +# Logging and tracing +tracing = "0.1" +tracing-subscriber = "0.3" + +# Metrics +prometheus = "0.14" + +# Utilities +xxhash-rust = { version = "0.8", features = ["xxh3"] } +dirs = "6.0" +chrono = "0.4" +uuid = "1.19" +which = "8.0" + +# Testing +tokio-test = "0.4" +approx = "0.5" +tempfile = "3.24" diff --git a/README.md b/README.md index 7a0b8f9..eca608d 100644 --- a/README.md +++ b/README.md @@ -131,16 +131,29 @@ StratumIOps is not a single project. It's the **orchestration layer** that coord - **Integration Patterns**: How projects work together - **Shared Standards**: Language guidelines (Rust, Nickel, Nushell, Bash) +### Stratum Crates + +Shared infrastructure libraries for the ecosystem: + +| Crate | Description | Status | +| ----- | ----------- | ------ | +| **stratum-embeddings** | Unified embedding providers with caching, fallback, and VectorStore trait | Proposed | +| **stratum-llm** | Unified LLM providers with CLI detection, circuit breaker, and caching | Proposed | + +See [Architecture ADRs](docs/en/architecture/adrs/) for detailed design decisions. + ### Documentation Structure ```text docs/ ├── en/ # English documentation │ ├── ia/ # AI/Development track -│ └── ops/ # Ops/DevOps track +│ ├── ops/ # Ops/DevOps track +│ └── architecture/ # Architecture decisions (ADRs) └── es/ # Spanish documentation ├── ia/ # AI/Development track - └── ops/ # Ops/DevOps track + ├── ops/ # Ops/DevOps track + └── architecture/ # Architecture decisions (ADRs) ``` ### Branding Assets diff --git a/crates/stratum-embeddings/Cargo.toml b/crates/stratum-embeddings/Cargo.toml new file mode 100644 index 0000000..a0eb10c --- /dev/null +++ b/crates/stratum-embeddings/Cargo.toml @@ -0,0 +1,113 @@ +[package] +name = "stratum-embeddings" +version = "0.1.0" +edition.workspace = true +description = "Unified embedding providers with caching, batch processing, and vector storage" +license.workspace = true + +[dependencies] +# Async runtime +tokio = { workspace = true } +async-trait = { workspace = true } +futures = { workspace = true } + +# HTTP client (for cloud providers) +reqwest = { workspace = true, optional = true } + +# Serialization +serde = { workspace = true } +serde_json = { workspace = true } +humantime-serde = { workspace = true } + +# Caching +moka = { workspace = true } + +# Persistent cache (optional) +sled = { workspace = true, optional = true } + +# Local embeddings +fastembed = { workspace = true, optional = true } + +# Vector storage backends +lancedb = { workspace = true, optional = true } +surrealdb = { workspace = true, optional = true } +arrow = { workspace = true, optional = true } + +# Error handling +thiserror = { workspace = true } + +# Logging +tracing = { workspace = true } + +# Metrics +prometheus = { workspace = true, optional = true } + +# Utilities +xxhash-rust = { workspace = true } + +[features] +default = ["fastembed-provider", "memory-cache"] + +# Providers +fastembed-provider = ["fastembed"] +openai-provider = ["reqwest"] +ollama-provider = ["reqwest"] +cohere-provider = ["reqwest"] +voyage-provider = ["reqwest"] +huggingface-provider = ["reqwest"] +all-providers = [ + "fastembed-provider", + "openai-provider", + "ollama-provider", + "cohere-provider", + "voyage-provider", + "huggingface-provider", +] + +# Cache backends +memory-cache = [] +persistent-cache = ["sled"] +all-cache = ["memory-cache", "persistent-cache"] + +# Vector storage backends +lancedb-store = ["lancedb", "arrow"] +surrealdb-store = ["surrealdb"] +all-stores = ["lancedb-store", "surrealdb-store"] + +# Observability +metrics = ["prometheus"] + +# Project-specific presets +kogral = ["fastembed-provider", "memory-cache", "surrealdb-store"] +provisioning = ["openai-provider", "memory-cache", "lancedb-store"] +vapora = ["all-providers", "memory-cache", "lancedb-store"] # Includes huggingface-provider + +# Full feature set +full = ["all-providers", "all-cache", "all-stores", "metrics"] + +[dev-dependencies] +tokio-test = { workspace = true } +approx = { workspace = true } +tempfile = { workspace = true } +tracing-subscriber = { workspace = true } + +# Example-specific feature requirements +[[example]] +name = "basic_usage" +required-features = ["fastembed-provider"] + +[[example]] +name = "fallback_demo" +required-features = ["ollama-provider", "fastembed-provider"] + +[[example]] +name = "lancedb_usage" +required-features = ["lancedb-store", "fastembed-provider"] + +[[example]] +name = "surrealdb_usage" +required-features = ["surrealdb-store", "fastembed-provider"] + +[[example]] +name = "huggingface_usage" +required-features = ["huggingface-provider"] diff --git a/crates/stratum-embeddings/README.md b/crates/stratum-embeddings/README.md new file mode 100644 index 0000000..613c564 --- /dev/null +++ b/crates/stratum-embeddings/README.md @@ -0,0 +1,180 @@ +# stratum-embeddings + +Unified embedding providers with caching, batch processing, and vector storage for the STRATUMIOPS ecosystem. + +## Features + +- **Multiple Providers**: FastEmbed (local), OpenAI, Ollama +- **Smart Caching**: In-memory caching with configurable TTL +- **Batch Processing**: Efficient batch embedding with automatic chunking +- **Vector Storage**: LanceDB (scale-first) and SurrealDB (graph-first) +- **Fallback Support**: Automatic failover between providers +- **Feature Flags**: Modular compilation for minimal dependencies + +## Architecture + +```text +┌─────────────────────────────────────────┐ +│ EmbeddingService │ +│ (facade with caching + fallback) │ +└─────────────┬───────────────────────────┘ + │ + ┌─────────┴─────────┐ + ▼ ▼ +┌─────────────┐ ┌─────────────┐ +│ Providers │ │ Cache │ +│ │ │ │ +│ • FastEmbed │ │ • Memory │ +│ • OpenAI │ │ • (Sled) │ +│ • Ollama │ │ │ +└─────────────┘ └─────────────┘ +``` + +## Quick Start + +### Basic Usage + +```rust +use stratum_embeddings::{ + EmbeddingService, FastEmbedProvider, MemoryCache, EmbeddingOptions +}; +use std::time::Duration; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let provider = FastEmbedProvider::small()?; + let cache = MemoryCache::new(1000, Duration::from_secs(300)); + let service = EmbeddingService::new(provider).with_cache(cache); + + let options = EmbeddingOptions::default_with_cache(); + let embedding = service.embed("Hello world", &options).await?; + + println!("Generated {} dimensions", embedding.len()); + Ok(()) +} +``` + +### Batch Processing + +```rust +let texts = vec![ + "Text 1".to_string(), + "Text 2".to_string(), + "Text 3".to_string(), +]; + +let result = service.embed_batch(texts, &options).await?; +println!("Embeddings: {}, Cached: {}", + result.embeddings.len(), + result.cached_count +); +``` + +### Vector Storage + +#### LanceDB (Provisioning, Vapora) + +```rust +use stratum_embeddings::{LanceDbStore, VectorStore, VectorStoreConfig}; + +let config = VectorStoreConfig::new(384); +let store = LanceDbStore::new("./data", "embeddings", config).await?; + +store.upsert("doc1", &embedding, metadata).await?; +let results = store.search(&query_embedding, 10, None).await?; +``` + +#### SurrealDB (Kogral) + +```rust +use stratum_embeddings::{SurrealDbStore, VectorStore, VectorStoreConfig}; + +let config = VectorStoreConfig::new(384); +let store = SurrealDbStore::new_memory("concepts", config).await?; + +store.upsert("concept1", &embedding, metadata).await?; +let results = store.search(&query_embedding, 10, None).await?; +``` + +## Feature Flags + +### Providers + +- `fastembed-provider` (default) - Local embeddings via fastembed +- `openai-provider` - OpenAI API embeddings +- `ollama-provider` - Ollama local server embeddings +- `all-providers` - All embedding providers + +### Cache + +- `memory-cache` (default) - In-memory caching with moka +- `persistent-cache` - Persistent cache with sled +- `all-cache` - All cache backends + +### Vector Storage + +- `lancedb-store` - LanceDB vector storage (columnar, disk-native) +- `surrealdb-store` - SurrealDB vector storage (graph + vector) +- `all-stores` - All storage backends + +### Project Presets + +- `kogral` - fastembed + memory + surrealdb +- `provisioning` - openai + memory + lancedb +- `vapora` - all-providers + memory + lancedb +- `full` - Everything enabled + +## Examples + +Run examples with: + +```bash +cargo run --example basic_usage --features=default +cargo run --example fallback_demo --features=fastembed-provider,ollama-provider +cargo run --example lancedb_usage --features=lancedb-store +cargo run --example surrealdb_usage --features=surrealdb-store +``` + +## Provider Comparison + +| Provider | Type | Cost | Dimensions | Use Case | +|----------|------|------|------------|----------| +| FastEmbed | Local | Free | 384-1024 | Dev, privacy-first | +| OpenAI | Cloud | $0.02-0.13/1M | 1536-3072 | Production RAG | +| Ollama | Local | Free | 384-1024 | Self-hosted | + +## Storage Backend Comparison + +| Backend | Best For | Strength | Scale | +|---------|----------|----------|-------| +| LanceDB | RAG, traces | Columnar, IVF-PQ index | Billions | +| SurrealDB | Knowledge graphs | Unified graph+vector queries | Millions | + +## Configuration + +Environment variables: + +```bash +# FastEmbed +FASTEMBED_MODEL=bge-small-en + +# OpenAI +OPENAI_API_KEY=sk-... +OPENAI_MODEL=text-embedding-3-small + +# Ollama +OLLAMA_MODEL=nomic-embed-text +OLLAMA_BASE_URL=http://localhost:11434 +``` + +## Development + +```bash +cargo check -p stratum-embeddings --all-features +cargo test -p stratum-embeddings --all-features +cargo clippy -p stratum-embeddings --all-features -- -D warnings +``` + +## License + +MIT OR Apache-2.0 diff --git a/crates/stratum-embeddings/docs/huggingface-provider.md b/crates/stratum-embeddings/docs/huggingface-provider.md new file mode 100644 index 0000000..0c9e0fc --- /dev/null +++ b/crates/stratum-embeddings/docs/huggingface-provider.md @@ -0,0 +1,346 @@ +# HuggingFace Embedding Provider + +Provider for HuggingFace Inference API embeddings with support for popular sentence-transformers and BGE models. + +## Overview + +The HuggingFace provider uses the free Inference API to generate embeddings. It supports: + +- **Public Models**: Free access to popular embedding models +- **Custom Models**: Support for any HuggingFace model with feature-extraction pipeline +- **Automatic Caching**: Built-in memory cache reduces API calls +- **Response Normalization**: Optional L2 normalization for similarity search + +## Features + +- ✅ Zero cost for public models (free Inference API) +- ✅ Support for 5+ popular models out of the box +- ✅ Custom model support with configurable dimensions +- ✅ Automatic retry with exponential backoff +- ✅ Rate limit handling +- ✅ Integration with stratum-embeddings caching layer + +## Supported Models + +### Predefined Models + +| Model | Dimensions | Use Case | Constructor | +|-------|------------|----------|-------------| +| **BAAI/bge-small-en-v1.5** | 384 | General-purpose, efficient | `HuggingFaceProvider::bge_small()` | +| **BAAI/bge-base-en-v1.5** | 768 | Balanced performance | `HuggingFaceProvider::bge_base()` | +| **BAAI/bge-large-en-v1.5** | 1024 | High quality | `HuggingFaceProvider::bge_large()` | +| **sentence-transformers/all-MiniLM-L6-v2** | 384 | Fast, lightweight | `HuggingFaceProvider::all_minilm()` | +| **sentence-transformers/all-mpnet-base-v2** | 768 | Strong baseline | - | + +### Custom Models + +```rust +let model = HuggingFaceModel::Custom( + "sentence-transformers/paraphrase-MiniLM-L6-v2".to_string(), + 384, +); +let provider = HuggingFaceProvider::new(api_key, model)?; +``` + +## API Rate Limits + +### Free Inference API + +HuggingFace Inference API has the following rate limits: + +| Tier | Requests/Hour | Requests/Day | Max Concurrent | +|------|---------------|--------------|----------------| +| **Anonymous** | 1,000 | 10,000 | 1 | +| **Free Account** | 3,000 | 30,000 | 3 | +| **PRO ($9/mo)** | 10,000 | 100,000 | 10 | +| **Enterprise** | Custom | Custom | Custom | + +**Rate Limit Headers**: +``` +X-RateLimit-Limit: 3000 +X-RateLimit-Remaining: 2999 +X-RateLimit-Reset: 1234567890 +``` + +### Rate Limit Handling + +The provider automatically handles rate limits with: + +1. **Exponential Backoff**: Retries with increasing delays (1s, 2s, 4s, 8s) +2. **Max Retries**: Default 3 retries before failing +3. **Circuit Breaker**: Automatically pauses requests if rate limited repeatedly +4. **Cache Integration**: Reduces API calls by 70-90% for repeated queries + +**Configuration**: +```rust +// Default retry config (built-in) +let provider = HuggingFaceProvider::new(api_key, model)?; + +// With custom retry (future enhancement) +let provider = HuggingFaceProvider::new(api_key, model)? + .with_retry_config(RetryConfig { + max_retries: 5, + initial_delay: Duration::from_secs(2), + max_delay: Duration::from_secs(30), + }); +``` + +### Best Practices for Rate Limits + +1. **Enable Caching**: Use `EmbeddingOptions::default_with_cache()` + ```rust + let options = EmbeddingOptions::default_with_cache(); + let embedding = provider.embed(text, &options).await?; + ``` + +2. **Batch Requests Carefully**: HuggingFace Inference API processes requests sequentially + ```rust + // This makes N API calls sequentially + let texts = vec!["text1", "text2", "text3"]; + let result = provider.embed_batch(&texts, &options).await?; + ``` + +3. **Use PRO Account for Production**: Free tier is suitable for development only + +4. **Monitor Rate Limits**: Check response headers + ```rust + // Future enhancement - rate limit monitoring + let stats = provider.rate_limit_stats(); + println!("Remaining: {}/{}", stats.remaining, stats.limit); + ``` + +## Authentication + +### Environment Variables + +The provider checks for API keys in this order: + +1. `HUGGINGFACE_API_KEY` +2. `HF_TOKEN` (alternative name) + +```bash +export HUGGINGFACE_API_KEY="hf_xxxxxxxxxxxxxxxxxxxx" +``` + +### Getting an API Token + +1. Go to [HuggingFace Settings](https://huggingface.co/settings/tokens) +2. Click "New token" +3. Select "Read" access (sufficient for Inference API) +4. Copy the token starting with `hf_` + +## Usage Examples + +### Basic Usage + +```rust +use stratum_embeddings::{HuggingFaceProvider, EmbeddingOptions}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Using predefined model + let provider = HuggingFaceProvider::bge_small()?; + + let options = EmbeddingOptions::default_with_cache(); + let embedding = provider.embed("Hello world", &options).await?; + + println!("Dimensions: {}", embedding.len()); // 384 + Ok(()) +} +``` + +### With EmbeddingService (Recommended) + +```rust +use std::time::Duration; +use stratum_embeddings::{ + HuggingFaceProvider, EmbeddingService, MemoryCache, EmbeddingOptions +}; + +let provider = HuggingFaceProvider::bge_small()?; +let cache = MemoryCache::new(1000, Duration::from_secs(3600)); + +let service = EmbeddingService::new(provider) + .with_cache(cache); + +let options = EmbeddingOptions::default_with_cache(); +let embedding = service.embed("Cached embeddings", &options).await?; +``` + +### Semantic Similarity Search + +```rust +use stratum_embeddings::{HuggingFaceProvider, EmbeddingOptions, cosine_similarity}; + +let provider = HuggingFaceProvider::bge_small()?; +let options = EmbeddingOptions { + normalize: true, // Important for cosine similarity + truncate: true, + use_cache: true, +}; + +let query = "machine learning"; +let doc1 = "deep learning and neural networks"; +let doc2 = "cooking recipes"; + +let query_emb = provider.embed(query, &options).await?; +let doc1_emb = provider.embed(doc1, &options).await?; +let doc2_emb = provider.embed(doc2, &options).await?; + +let sim1 = cosine_similarity(&query_emb, &doc1_emb); +let sim2 = cosine_similarity(&query_emb, &doc2_emb); + +println!("Similarity with doc1: {:.4}", sim1); // ~0.85 +println!("Similarity with doc2: {:.4}", sim2); // ~0.15 +``` + +### Custom Model + +```rust +use stratum_embeddings::{HuggingFaceProvider, HuggingFaceModel}; + +let api_key = std::env::var("HUGGINGFACE_API_KEY")?; +let model = HuggingFaceModel::Custom( + "intfloat/multilingual-e5-large".to_string(), + 1024, // Specify dimensions +); + +let provider = HuggingFaceProvider::new(api_key, model)?; +``` + +## Error Handling + +### Common Errors + +| Error | Cause | Solution | +|-------|-------|----------| +| `ConfigError: API key is empty` | Missing credentials | Set `HUGGINGFACE_API_KEY` | +| `ApiError: HTTP 401` | Invalid API token | Check token validity | +| `ApiError: HTTP 429` | Rate limit exceeded | Wait or upgrade tier | +| `ApiError: HTTP 503` | Model loading | Retry after ~20s | +| `DimensionMismatch` | Wrong model dimensions | Update `Custom` model dims | + +### Retry Example + +```rust +use tokio::time::sleep; +use std::time::Duration; + +let mut retries = 0; +let max_retries = 3; + +loop { + match provider.embed(text, &options).await { + Ok(embedding) => break Ok(embedding), + Err(e) if e.to_string().contains("429") && retries < max_retries => { + retries += 1; + let delay = Duration::from_secs(2u64.pow(retries)); + eprintln!("Rate limited, retrying in {:?}...", delay); + sleep(delay).await; + } + Err(e) => break Err(e), + } +} +``` + +## Performance Characteristics + +### Latency + +| Operation | Latency | Notes | +|-----------|---------|-------| +| **Single embed** | 200-500ms | Depends on model size and region | +| **Batch (N items)** | N × 200-500ms | Sequential processing | +| **Cache hit** | <1ms | In-memory lookup | +| **Cold start** | +5-20s | First request loads model | + +### Throughput + +| Tier | Max RPS | Daily Limit | +|------|---------|-------------| +| Free | ~0.8 | 30,000 | +| PRO | ~2.8 | 100,000 | + +**With Caching** (80% hit rate): +- Free tier: ~4 effective RPS +- PRO tier: ~14 effective RPS + +## Cost Comparison + +| Provider | Cost/1M Tokens | Free Tier | Notes | +|----------|----------------|-----------|-------| +| **HuggingFace** | $0.00 | 30k req/day | Free for public models | +| OpenAI | $0.02-0.13 | $5 credit | Pay per token | +| Cohere | $0.10 | 100 req/month | Limited free tier | +| Voyage | $0.12 | None | No free tier | + +## Limitations + +1. **No True Batching**: Inference API processes one request at a time +2. **Cold Starts**: Models need ~20s to load on first request +3. **Rate Limits**: Free tier suitable for development only +4. **Regional Latency**: Single region (US/EU), no edge locations +5. **Model Loading**: Popular models cached, custom models may be slow + +## Advanced Configuration + +### Model Loading Timeout + +```rust +// Future enhancement +let provider = HuggingFaceProvider::new(api_key, model)? + .with_timeout(Duration::from_secs(120)); // Wait longer for cold starts +``` + +### Dedicated Inference Endpoints + +For production workloads, consider [Dedicated Endpoints](https://huggingface.co/inference-endpoints): + +- True batch processing +- Guaranteed uptime +- No rate limits +- Custom regions +- ~$60-500/month + +## Migration Guide + +### From vapora Custom Implementation + +**Before**: +```rust +let hf = HuggingFaceEmbedding::new(api_key, "BAAI/bge-small-en-v1.5".to_string()); +let embedding = hf.embed(text).await?; +``` + +**After**: +```rust +let provider = HuggingFaceProvider::bge_small()?; +let options = EmbeddingOptions::default_with_cache(); +let embedding = provider.embed(text, &options).await?; +``` + +### From OpenAI + +```rust +// OpenAI (paid) +let provider = OpenAiProvider::new(api_key, OpenAiModel::TextEmbedding3Small)?; + +// HuggingFace (free, similar quality) +let provider = HuggingFaceProvider::bge_small()?; +``` + +## Running the Example + +```bash +export HUGGINGFACE_API_KEY="hf_xxxxxxxxxxxxxxxxxxxx" + +cargo run --example huggingface_usage \ + --features huggingface-provider +``` + +## References + +- [HuggingFace Inference API Docs](https://huggingface.co/docs/api-inference/index) +- [BGE Embedding Models](https://huggingface.co/BAAI) +- [Sentence Transformers](https://www.sbert.net/) +- [Rate Limits Documentation](https://huggingface.co/docs/api-inference/rate-limits) diff --git a/crates/stratum-embeddings/examples/basic_usage.rs b/crates/stratum-embeddings/examples/basic_usage.rs new file mode 100644 index 0000000..4388d8d --- /dev/null +++ b/crates/stratum-embeddings/examples/basic_usage.rs @@ -0,0 +1,50 @@ +use std::time::Duration; + +use stratum_embeddings::{EmbeddingOptions, EmbeddingService, FastEmbedProvider, MemoryCache}; +use tracing::info; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + info!("Initializing FastEmbed provider..."); + let provider = FastEmbedProvider::small()?; + + let cache = MemoryCache::new(1000, Duration::from_secs(300)); + let service = EmbeddingService::new(provider).with_cache(cache); + + info!("Service ready: {:?}", service.provider_info()); + + let options = EmbeddingOptions::default_with_cache(); + + info!("Embedding single text..."); + let text = "Stratum embeddings is a unified embedding library"; + let embedding = service.embed(text, &options).await?; + + info!("Generated embedding with {} dimensions", embedding.len()); + + info!("Embedding same text again (should be cached)..."); + let embedding2 = service.embed(text, &options).await?; + assert_eq!(embedding, embedding2); + info!("Cache hit confirmed!"); + + info!("Embedding batch of texts..."); + let texts = vec![ + "Rust is a systems programming language".to_string(), + "Knowledge graphs connect concepts".to_string(), + "Vector databases enable semantic search".to_string(), + ]; + + let result = service.embed_batch(texts, &options).await?; + + info!( + "Batch complete: {} embeddings generated", + result.embeddings.len() + ); + info!("Model: {}, Dimensions: {}", result.model, result.dimensions); + info!("Cached count: {}", result.cached_count); + + info!("Cache size: {}", service.cache_size()); + + Ok(()) +} diff --git a/crates/stratum-embeddings/examples/fallback_demo.rs b/crates/stratum-embeddings/examples/fallback_demo.rs new file mode 100644 index 0000000..bb62215 --- /dev/null +++ b/crates/stratum-embeddings/examples/fallback_demo.rs @@ -0,0 +1,44 @@ +use std::{sync::Arc, time::Duration}; + +use stratum_embeddings::{ + EmbeddingOptions, EmbeddingService, FastEmbedProvider, MemoryCache, OllamaProvider, +}; +use tracing::{info, warn}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + info!("Setting up primary provider (Ollama)..."); + let primary = OllamaProvider::default_model()?; + + info!("Setting up fallback provider (FastEmbed)..."); + let fallback = + Arc::new(FastEmbedProvider::small()?) as Arc; + + let cache = MemoryCache::new(1000, Duration::from_secs(300)); + let service = EmbeddingService::new(primary) + .with_cache(cache) + .with_fallback(fallback); + + let options = EmbeddingOptions::default_with_cache(); + + info!("Checking if Ollama is available..."); + if service.is_ready().await { + info!("Ollama is available, using as primary"); + } else { + warn!("Ollama not available, will fall back to FastEmbed"); + } + + info!("Embedding text (will use available provider)..."); + let text = "This demonstrates fallback strategy in action"; + let embedding = service.embed(text, &options).await?; + + info!( + "Successfully generated embedding with {} dimensions", + embedding.len() + ); + info!("Cache size: {}", service.cache_size()); + + Ok(()) +} diff --git a/crates/stratum-embeddings/examples/huggingface_usage.rs b/crates/stratum-embeddings/examples/huggingface_usage.rs new file mode 100644 index 0000000..6763c68 --- /dev/null +++ b/crates/stratum-embeddings/examples/huggingface_usage.rs @@ -0,0 +1,125 @@ +use std::time::Duration; + +use stratum_embeddings::{ + EmbeddingOptions, HuggingFaceModel, HuggingFaceProvider, MemoryCache, +}; +use tracing::info; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + info!("=== HuggingFace Embedding Provider Demo ==="); + + // Example 1: Using predefined model (bge-small) + info!("\n1. Using predefined BGE-small model (384 dimensions)"); + let provider = HuggingFaceProvider::bge_small()?; + let options = EmbeddingOptions::default_with_cache(); + + let text = "HuggingFace provides free inference API for embedding models"; + let embedding = provider.embed(text, &options).await?; + + info!( + "Generated embedding with {} dimensions from BGE-small", + embedding.len() + ); + info!("First 5 values: {:?}", &embedding[..5]); + + // Example 2: Using different model size + info!("\n2. Using BGE-base model (768 dimensions)"); + let provider = HuggingFaceProvider::bge_base()?; + let embedding = provider.embed(text, &options).await?; + + info!( + "Generated embedding with {} dimensions from BGE-base", + embedding.len() + ); + + // Example 3: Using custom model + info!("\n3. Using custom model"); + let api_key = std::env::var("HUGGINGFACE_API_KEY") + .or_else(|_| std::env::var("HF_TOKEN")) + .expect("Set HUGGINGFACE_API_KEY or HF_TOKEN"); + + let custom_model = HuggingFaceModel::Custom( + "sentence-transformers/paraphrase-MiniLM-L6-v2".to_string(), + 384, + ); + let provider = HuggingFaceProvider::new(api_key, custom_model)?; + let embedding = provider.embed(text, &options).await?; + + info!( + "Custom model embedding: {} dimensions", + embedding.len() + ); + + // Example 4: Batch embeddings (sequential requests to HF API) + info!("\n4. Batch embedding (sequential API calls)"); + let provider = HuggingFaceProvider::all_minilm()?; + + let texts = vec![ + "First document about embeddings", + "Second document about transformers", + "Third document about NLP", + ]; + + let text_refs: Vec<&str> = texts.iter().map(|s| s.as_str()).collect(); + let result = provider.embed_batch(&text_refs, &options).await?; + + info!("Embedded {} texts", result.embeddings.len()); + for (i, emb) in result.embeddings.iter().enumerate() { + info!(" Text {}: {} dimensions", i + 1, emb.len()); + } + + // Example 5: Using with cache + info!("\n5. Demonstrating cache effectiveness"); + let cache = MemoryCache::new(1000, Duration::from_secs(300)); + let service = stratum_embeddings::EmbeddingService::new( + HuggingFaceProvider::bge_small()? + ).with_cache(cache); + + let cached_options = EmbeddingOptions::default_with_cache(); + + // First call - cache miss + let start = std::time::Instant::now(); + let _ = service.embed(text, &cached_options).await?; + let first_duration = start.elapsed(); + info!("First call (cache miss): {:?}", first_duration); + + // Second call - cache hit + let start = std::time::Instant::now(); + let _ = service.embed(text, &cached_options).await?; + let second_duration = start.elapsed(); + info!("Second call (cache hit): {:?}", second_duration); + info!("Speedup: {:.2}x", first_duration.as_secs_f64() / second_duration.as_secs_f64()); + info!("Cache size: {}", service.cache_size()); + + // Example 6: Normalized embeddings for similarity search + info!("\n6. Normalized embeddings for similarity"); + let provider = HuggingFaceProvider::bge_small()?; + let normalize_options = EmbeddingOptions { + normalize: true, + truncate: true, + use_cache: true, + }; + + let query = "machine learning embeddings"; + let doc1 = "neural network embeddings for NLP"; + let doc2 = "cooking recipes and ingredients"; + + let query_emb = provider.embed(query, &normalize_options).await?; + let doc1_emb = provider.embed(doc1, &normalize_options).await?; + let doc2_emb = provider.embed(doc2, &normalize_options).await?; + + let sim1 = stratum_embeddings::cosine_similarity(&query_emb, &doc1_emb); + let sim2 = stratum_embeddings::cosine_similarity(&query_emb, &doc2_emb); + + info!("Query: '{}'", query); + info!("Similarity with doc1 ('{}'): {:.4}", doc1, sim1); + info!("Similarity with doc2 ('{}'): {:.4}", doc2, sim2); + info!("Most similar: {}", if sim1 > sim2 { "doc1" } else { "doc2" }); + + info!("\n=== Demo Complete ==="); + + Ok(()) +} diff --git a/crates/stratum-embeddings/examples/lancedb_usage.rs b/crates/stratum-embeddings/examples/lancedb_usage.rs new file mode 100644 index 0000000..56b8465 --- /dev/null +++ b/crates/stratum-embeddings/examples/lancedb_usage.rs @@ -0,0 +1,67 @@ +use std::time::Duration; + +use stratum_embeddings::{ + EmbeddingOptions, EmbeddingService, FastEmbedProvider, LanceDbStore, MemoryCache, VectorStore, + VectorStoreConfig, +}; +use tempfile::tempdir; +use tracing::info; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + info!("Initializing embedding service..."); + let provider = FastEmbedProvider::small()?; + let cache = MemoryCache::new(1000, Duration::from_secs(300)); + let service = EmbeddingService::new(provider).with_cache(cache); + + let dir = tempdir()?; + let db_path = dir.path().to_str().unwrap(); + + info!("Creating LanceDB store at: {}", db_path); + let config = VectorStoreConfig::new(384); + let store = LanceDbStore::new(db_path, "embeddings", config).await?; + + let documents = vec![ + ( + "doc1", + "Rust provides memory safety without garbage collection", + ), + ("doc2", "Knowledge graphs represent structured information"), + ("doc3", "Vector databases enable semantic similarity search"), + ("doc4", "Machine learning models learn from data patterns"), + ("doc5", "Embeddings capture semantic meaning in vectors"), + ]; + + info!("Embedding and storing {} documents...", documents.len()); + let options = EmbeddingOptions::default_with_cache(); + + for (id, text) in &documents { + let embedding = service.embed(text, &options).await?; + let metadata = serde_json::json!({ + "text": text, + "source": "demo" + }); + store.upsert(id, &embedding, metadata).await?; + } + + info!("Documents stored successfully"); + + info!("Performing semantic search..."); + let query = "How do databases support similarity matching?"; + let query_embedding = service.embed(query, &options).await?; + + let results = store.search(&query_embedding, 3, None).await?; + + info!("Search results for: '{}'", query); + for (i, result) in results.iter().enumerate() { + let text = result.metadata["text"].as_str().unwrap_or("N/A"); + info!(" {}. [score: {:.4}] {}", i + 1, result.score, text); + } + + let count = store.count().await?; + info!("Total documents in store: {}", count); + + Ok(()) +} diff --git a/crates/stratum-embeddings/examples/surrealdb_usage.rs b/crates/stratum-embeddings/examples/surrealdb_usage.rs new file mode 100644 index 0000000..e25ba79 --- /dev/null +++ b/crates/stratum-embeddings/examples/surrealdb_usage.rs @@ -0,0 +1,66 @@ +use std::time::Duration; + +use stratum_embeddings::{ + EmbeddingOptions, EmbeddingService, FastEmbedProvider, MemoryCache, SurrealDbStore, + VectorStore, VectorStoreConfig, +}; +use tracing::info; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + info!("Initializing embedding service..."); + let provider = FastEmbedProvider::small()?; + let cache = MemoryCache::new(1000, Duration::from_secs(300)); + let service = EmbeddingService::new(provider).with_cache(cache); + + info!("Creating SurrealDB in-memory store..."); + let config = VectorStoreConfig::new(384); + let store = SurrealDbStore::new_memory("concepts", config).await?; + + let concepts = vec![ + ("ownership", "Rust's ownership system prevents memory leaks"), + ( + "borrowing", + "Borrowing allows references without ownership transfer", + ), + ("lifetimes", "Lifetimes ensure references remain valid"), + ("traits", "Traits define shared behavior across types"), + ("generics", "Generics enable code reuse with type safety"), + ]; + + info!("Embedding and storing {} concepts...", concepts.len()); + let options = EmbeddingOptions::default_with_cache(); + + for (id, description) in &concepts { + let embedding = service.embed(description, &options).await?; + let metadata = serde_json::json!({ + "concept": id, + "description": description, + "language": "rust" + }); + store.upsert(id, &embedding, metadata).await?; + } + + info!("Concepts stored successfully"); + + info!("Performing knowledge graph search..."); + let query = "How does Rust manage memory?"; + let query_embedding = service.embed(query, &options).await?; + + let results = store.search(&query_embedding, 3, None).await?; + + info!("Most relevant concepts for: '{}'", query); + for (i, result) in results.iter().enumerate() { + let concept = result.metadata["concept"].as_str().unwrap_or("N/A"); + let description = result.metadata["description"].as_str().unwrap_or("N/A"); + info!(" {}. {} [score: {:.4}]", i + 1, concept, result.score); + info!(" {}", description); + } + + let count = store.count().await?; + info!("Total concepts in graph: {}", count); + + Ok(()) +} diff --git a/crates/stratum-embeddings/src/batch.rs b/crates/stratum-embeddings/src/batch.rs new file mode 100644 index 0000000..d8b4fd7 --- /dev/null +++ b/crates/stratum-embeddings/src/batch.rs @@ -0,0 +1,312 @@ +use std::sync::Arc; + +use futures::stream::{self, StreamExt}; +use tracing::{debug, info}; + +use crate::{ + cache::{cache_key, EmbeddingCache}, + error::EmbeddingError, + traits::{Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult}, +}; + +pub struct BatchProcessor { + provider: Arc

, + cache: Option>, + max_concurrent: usize, +} + +impl BatchProcessor { + pub fn new(provider: Arc

, cache: Option>) -> Self { + Self { + provider, + cache, + max_concurrent: 10, + } + } + + pub fn with_concurrency(mut self, max_concurrent: usize) -> Self { + self.max_concurrent = max_concurrent; + self + } + + pub async fn process_batch( + &self, + texts: Vec, + options: &EmbeddingOptions, + ) -> Result { + if texts.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Texts cannot be empty".to_string(), + )); + } + + let provider_batch_size = self.provider.max_batch_size(); + let cache_enabled = options.use_cache && self.cache.is_some(); + + let mut all_embeddings = Vec::with_capacity(texts.len()); + let mut total_tokens = 0u32; + let mut cached_count = 0usize; + + for chunk in texts.chunks(provider_batch_size) { + let (cache_hits, cache_misses) = if cache_enabled { + self.check_cache(chunk, self.provider.name(), self.provider.model()) + .await + } else { + (vec![None; chunk.len()], (0..chunk.len()).collect()) + }; + + let mut chunk_embeddings = cache_hits; + cached_count += chunk_embeddings.iter().filter(|e| e.is_some()).count(); + + if !cache_misses.is_empty() { + let texts_to_embed: Vec<&str> = cache_misses + .iter() + .map(|&idx| chunk[idx].as_str()) + .collect(); + + debug!( + "Embedding {} texts (cached: {}, new: {})", + chunk.len(), + cached_count, + texts_to_embed.len() + ); + + let result = self.provider.embed_batch(&texts_to_embed, options).await?; + + if let Some(tokens) = result.total_tokens { + total_tokens += tokens; + } + + if let Some(cache) = cache_enabled.then_some(self.cache.as_ref()).flatten() { + let cache_items = Self::build_cache_items( + self.provider.name(), + self.provider.model(), + chunk, + &cache_misses, + &result.embeddings, + ); + cache.insert_batch(cache_items).await; + } + + for (miss_idx, embedding) in cache_misses.iter().zip(result.embeddings.into_iter()) + { + chunk_embeddings[*miss_idx] = Some(embedding); + } + } + + all_embeddings.extend( + chunk_embeddings + .into_iter() + .map(|e| e.expect("Missing embedding")), + ); + } + + info!( + "Batch complete: {} embeddings ({} cached, {} new)", + texts.len(), + cached_count, + texts.len() - cached_count + ); + + Ok(EmbeddingResult { + embeddings: all_embeddings, + model: self.provider.model().to_string(), + dimensions: self.provider.dimensions(), + total_tokens: if total_tokens > 0 { + Some(total_tokens) + } else { + None + }, + cached_count, + }) + } + + pub async fn process_stream( + &self, + texts: Vec, + options: &EmbeddingOptions, + ) -> Result, EmbeddingError> { + let provider = Arc::clone(&self.provider); + let cache = self.cache.clone(); + let provider_name = provider.name().to_string(); + let provider_model = provider.model().to_string(); + let opts = options.clone(); + + let embeddings: Vec = stream::iter(texts) + .map(move |text| { + let provider = Arc::clone(&provider); + let cache = cache.clone(); + let provider_name = provider_name.clone(); + let provider_model = provider_model.clone(); + let opts = opts.clone(); + + Self::embed_with_cache(provider, cache, text, provider_name, provider_model, opts) + }) + .buffer_unordered(self.max_concurrent) + .collect::>>() + .await + .into_iter() + .collect::, _>>()?; + + Ok(embeddings) + } + + fn build_cache_items( + provider_name: &str, + provider_model: &str, + chunk: &[String], + cache_misses: &[usize], + embeddings: &[Embedding], + ) -> Vec<(String, Embedding)> { + cache_misses + .iter() + .zip(embeddings.iter()) + .map(|(&idx, emb)| { + ( + cache_key(provider_name, provider_model, &chunk[idx]), + emb.clone(), + ) + }) + .collect() + } + + async fn embed_with_cache( + provider: Arc

, + cache: Option>, + text: String, + provider_name: String, + provider_model: String, + opts: EmbeddingOptions, + ) -> Result { + let key = cache_key(&provider_name, &provider_model, &text); + + if opts.use_cache { + if let Some(cached) = Self::try_get_cached(&cache, &key).await { + return Ok(cached); + } + } + + let embedding = provider.embed(&text, &opts).await?; + + if opts.use_cache { + Self::cache_insert(&cache, &key, embedding.clone()).await; + } + + Ok(embedding) + } + + async fn try_get_cached(cache: &Option>, key: &str) -> Option { + match cache { + Some(c) => c.get(key).await, + None => None, + } + } + + async fn cache_insert(cache: &Option>, key: &str, embedding: Embedding) { + if let Some(c) = cache { + c.insert(key, embedding).await; + } + } + + async fn check_cache( + &self, + texts: &[String], + provider_name: &str, + model_name: &str, + ) -> (Vec>, Vec) { + let cache = match &self.cache { + Some(c) => c, + None => return (vec![None; texts.len()], (0..texts.len()).collect()), + }; + + let keys: Vec = texts + .iter() + .map(|text| cache_key(provider_name, model_name, text)) + .collect(); + + let cached = cache.get_batch(&keys).await; + let misses: Vec = cached + .iter() + .enumerate() + .filter(|(_, e)| e.is_none()) + .map(|(i, _)| i) + .collect(); + + (cached, misses) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::*; + use crate::{cache::MemoryCache, providers::FastEmbedProvider}; + + #[tokio::test] + async fn test_batch_processor_no_cache() { + let provider = Arc::new(FastEmbedProvider::small().expect("Failed to init")); + let processor: BatchProcessor<_, MemoryCache> = BatchProcessor::new(provider, None); + + let texts = vec!["Hello world".to_string(), "Goodbye world".to_string()]; + + let options = EmbeddingOptions::no_cache(); + let result = processor + .process_batch(texts, &options) + .await + .expect("Failed to process"); + + assert_eq!(result.embeddings.len(), 2); + assert_eq!(result.cached_count, 0); + } + + #[tokio::test] + async fn test_batch_processor_with_cache() { + let provider = Arc::new(FastEmbedProvider::small().expect("Failed to init")); + let cache = Arc::new(MemoryCache::new(100, Duration::from_secs(60))); + let processor = BatchProcessor::new(provider, Some(cache)); + + let texts = vec!["Hello world".to_string(), "Goodbye world".to_string()]; + + let options = EmbeddingOptions::default_with_cache(); + let result1 = processor + .process_batch(texts.clone(), &options) + .await + .expect("Failed first batch"); + + assert_eq!(result1.embeddings.len(), 2); + assert_eq!(result1.cached_count, 0); + + let result2 = processor + .process_batch(texts, &options) + .await + .expect("Failed second batch"); + + assert_eq!(result2.embeddings.len(), 2); + assert_eq!(result2.cached_count, 2); + + assert_eq!(result1.embeddings, result2.embeddings); + } + + #[tokio::test] + async fn test_batch_processor_stream() { + let provider = Arc::new(FastEmbedProvider::small().expect("Failed to init")); + let cache = Arc::new(MemoryCache::with_defaults()); + let processor = BatchProcessor::new(provider, Some(cache)).with_concurrency(2); + + let texts = vec![ + "Text 1".to_string(), + "Text 2".to_string(), + "Text 3".to_string(), + "Text 4".to_string(), + ]; + + let options = EmbeddingOptions::default_with_cache(); + let embeddings = processor + .process_stream(texts, &options) + .await + .expect("Failed stream"); + + assert_eq!(embeddings.len(), 4); + } +} diff --git a/crates/stratum-embeddings/src/cache/memory.rs b/crates/stratum-embeddings/src/cache/memory.rs new file mode 100644 index 0000000..d28aa17 --- /dev/null +++ b/crates/stratum-embeddings/src/cache/memory.rs @@ -0,0 +1,167 @@ +#[cfg(feature = "memory-cache")] +use std::time::Duration; + +#[cfg(feature = "memory-cache")] +use async_trait::async_trait; +#[cfg(feature = "memory-cache")] +use moka::future::Cache; + +#[cfg(feature = "memory-cache")] +use crate::{cache::EmbeddingCache, traits::Embedding}; + +pub struct MemoryCache { + cache: Cache, +} + +impl MemoryCache { + pub fn new(max_capacity: u64, ttl: Duration) -> Self { + let cache = Cache::builder() + .max_capacity(max_capacity) + .time_to_live(ttl) + .build(); + + Self { cache } + } + + pub fn with_defaults() -> Self { + Self::new(10_000, Duration::from_secs(3600)) + } + + pub fn unlimited(ttl: Duration) -> Self { + let cache = Cache::builder().time_to_live(ttl).build(); + + Self { cache } + } +} + +impl Default for MemoryCache { + fn default() -> Self { + Self::with_defaults() + } +} + +#[async_trait] +impl EmbeddingCache for MemoryCache { + async fn get(&self, key: &str) -> Option { + self.cache.get(key).await + } + + async fn insert(&self, key: &str, embedding: Embedding) { + self.cache.insert(key.to_string(), embedding).await; + self.cache.run_pending_tasks().await; + } + + async fn get_batch(&self, keys: &[String]) -> Vec> { + let mut results = Vec::with_capacity(keys.len()); + for key in keys { + results.push(self.cache.get(key).await); + } + results + } + + async fn insert_batch(&self, items: Vec<(String, Embedding)>) { + for (key, embedding) in items { + self.cache.insert(key, embedding).await; + } + self.cache.run_pending_tasks().await; + } + + async fn invalidate(&self, key: &str) { + self.cache.invalidate(key).await; + self.cache.run_pending_tasks().await; + } + + async fn clear(&self) { + self.cache.invalidate_all(); + self.cache.run_pending_tasks().await; + } + + fn size(&self) -> usize { + self.cache.entry_count() as usize + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_memory_cache_basic() { + let cache = MemoryCache::with_defaults(); + + let embedding = vec![1.0, 2.0, 3.0]; + cache.insert("test_key", embedding.clone()).await; + + let retrieved = cache.get("test_key").await; + assert_eq!(retrieved, Some(embedding)); + + let missing = cache.get("missing_key").await; + assert_eq!(missing, None); + } + + #[tokio::test] + async fn test_memory_cache_batch() { + let cache = MemoryCache::with_defaults(); + + let items = vec![ + ("key1".to_string(), vec![1.0, 2.0]), + ("key2".to_string(), vec![3.0, 4.0]), + ("key3".to_string(), vec![5.0, 6.0]), + ]; + + cache.insert_batch(items.clone()).await; + + let keys = vec![ + "key1".to_string(), + "key2".to_string(), + "missing".to_string(), + ]; + let results = cache.get_batch(&keys).await; + + assert_eq!(results.len(), 3); + assert_eq!(results[0], Some(vec![1.0, 2.0])); + assert_eq!(results[1], Some(vec![3.0, 4.0])); + assert_eq!(results[2], None); + } + + #[tokio::test] + async fn test_memory_cache_invalidate() { + let cache = MemoryCache::with_defaults(); + + cache.insert("key1", vec![1.0, 2.0]).await; + cache.insert("key2", vec![3.0, 4.0]).await; + + assert!(cache.get("key1").await.is_some()); + assert!(cache.get("key2").await.is_some()); + + cache.invalidate("key1").await; + + assert!(cache.get("key1").await.is_none()); + assert!(cache.get("key2").await.is_some()); + } + + #[tokio::test] + async fn test_memory_cache_clear() { + let cache = MemoryCache::with_defaults(); + + cache.insert("key1", vec![1.0]).await; + cache.insert("key2", vec![2.0]).await; + + cache.clear().await; + + assert!(cache.get("key1").await.is_none()); + assert!(cache.get("key2").await.is_none()); + } + + #[tokio::test] + async fn test_memory_cache_ttl() { + let cache = MemoryCache::new(1000, Duration::from_millis(100)); + + cache.insert("key1", vec![1.0, 2.0]).await; + assert!(cache.get("key1").await.is_some()); + + tokio::time::sleep(Duration::from_millis(150)).await; + + assert!(cache.get("key1").await.is_none()); + } +} diff --git a/crates/stratum-embeddings/src/cache/mod.rs b/crates/stratum-embeddings/src/cache/mod.rs new file mode 100644 index 0000000..be2a46d --- /dev/null +++ b/crates/stratum-embeddings/src/cache/mod.rs @@ -0,0 +1,47 @@ +#[cfg(feature = "memory-cache")] +pub mod memory; +#[cfg(feature = "persistent-cache")] +pub mod persistent; + +use async_trait::async_trait; +#[cfg(feature = "memory-cache")] +pub use memory::MemoryCache; +#[cfg(feature = "persistent-cache")] +pub use persistent::PersistentCache; + +use crate::traits::Embedding; + +#[async_trait] +pub trait EmbeddingCache: Send + Sync { + async fn get(&self, key: &str) -> Option; + async fn insert(&self, key: &str, embedding: Embedding); + async fn get_batch(&self, keys: &[String]) -> Vec>; + async fn insert_batch(&self, items: Vec<(String, Embedding)>); + async fn invalidate(&self, key: &str); + async fn clear(&self); + fn size(&self) -> usize; +} + +pub fn cache_key(provider: &str, model: &str, text: &str) -> String { + use xxhash_rust::xxh3::xxh3_64; + let hash = xxh3_64(format!("{}:{}:{}", provider, model, text).as_bytes()); + format!("{}:{}:{:x}", provider, model, hash) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cache_key_consistency() { + let key1 = cache_key("fastembed", "bge-small", "hello world"); + let key2 = cache_key("fastembed", "bge-small", "hello world"); + assert_eq!(key1, key2); + + let key3 = cache_key("fastembed", "bge-small", "hello world!"); + assert_ne!(key1, key3); + + let key4 = cache_key("openai", "bge-small", "hello world"); + assert_ne!(key1, key4); + } +} diff --git a/crates/stratum-embeddings/src/cache/persistent.rs b/crates/stratum-embeddings/src/cache/persistent.rs new file mode 100644 index 0000000..eb6d328 --- /dev/null +++ b/crates/stratum-embeddings/src/cache/persistent.rs @@ -0,0 +1,152 @@ +#[cfg(feature = "persistent-cache")] +use std::path::Path; + +#[cfg(feature = "persistent-cache")] +use async_trait::async_trait; +#[cfg(feature = "persistent-cache")] +use sled::Db; + +#[cfg(feature = "persistent-cache")] +use crate::{cache::EmbeddingCache, error::EmbeddingError, traits::Embedding}; + +pub struct PersistentCache { + db: Db, +} + +impl PersistentCache { + pub fn new>(path: P) -> Result { + let db = sled::open(path) + .map_err(|e| EmbeddingError::CacheError(format!("Failed to open sled db: {}", e)))?; + + Ok(Self { db }) + } + + pub fn in_memory() -> Result { + let db = sled::Config::new().temporary(true).open().map_err(|e| { + EmbeddingError::CacheError(format!("Failed to create in-memory sled db: {}", e)) + })?; + + Ok(Self { db }) + } + + fn serialize_embedding(embedding: &Embedding) -> Result, EmbeddingError> { + serde_json::to_vec(embedding) + .map_err(|e| EmbeddingError::SerializationError(format!("Embedding serialize: {}", e))) + } + + fn deserialize_embedding(data: &[u8]) -> Result { + serde_json::from_slice(data).map_err(|e| { + EmbeddingError::SerializationError(format!("Embedding deserialize: {}", e)) + }) + } +} + +#[async_trait] +impl EmbeddingCache for PersistentCache { + async fn get(&self, key: &str) -> Option { + self.db + .get(key) + .ok() + .flatten() + .and_then(|bytes| Self::deserialize_embedding(&bytes).ok()) + } + + async fn insert(&self, key: &str, embedding: Embedding) { + if let Ok(bytes) = Self::serialize_embedding(&embedding) { + let _ = self.db.insert(key, bytes); + let _ = self.db.flush(); + } + } + + async fn get_batch(&self, keys: &[String]) -> Vec> { + keys.iter() + .map(|key| { + self.db + .get(key) + .ok() + .flatten() + .and_then(|bytes| Self::deserialize_embedding(&bytes).ok()) + }) + .collect() + } + + async fn insert_batch(&self, items: Vec<(String, Embedding)>) { + for (key, embedding) in items { + if let Ok(bytes) = Self::serialize_embedding(&embedding) { + let _ = self.db.insert(key, bytes); + } + } + let _ = self.db.flush(); + } + + async fn invalidate(&self, key: &str) { + let _ = self.db.remove(key); + let _ = self.db.flush(); + } + + async fn clear(&self) { + let _ = self.db.clear(); + let _ = self.db.flush(); + } + + fn size(&self) -> usize { + self.db.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_persistent_cache_in_memory() { + let cache = PersistentCache::in_memory().expect("Failed to create cache"); + + let embedding = vec![1.0, 2.0, 3.0]; + cache.insert("test_key", embedding.clone()).await; + + let retrieved = cache.get("test_key").await; + assert_eq!(retrieved, Some(embedding)); + } + + #[tokio::test] + async fn test_persistent_cache_batch() { + let cache = PersistentCache::in_memory().expect("Failed to create cache"); + + let items = vec![ + ("key1".to_string(), vec![1.0, 2.0]), + ("key2".to_string(), vec![3.0, 4.0]), + ]; + + cache.insert_batch(items).await; + + let keys = vec!["key1".to_string(), "key2".to_string()]; + let results = cache.get_batch(&keys).await; + + assert_eq!(results[0], Some(vec![1.0, 2.0])); + assert_eq!(results[1], Some(vec![3.0, 4.0])); + } + + #[tokio::test] + async fn test_persistent_cache_invalidate() { + let cache = PersistentCache::in_memory().expect("Failed to create cache"); + + cache.insert("key1", vec![1.0]).await; + assert!(cache.get("key1").await.is_some()); + + cache.invalidate("key1").await; + assert!(cache.get("key1").await.is_none()); + } + + #[tokio::test] + async fn test_persistent_cache_clear() { + let cache = PersistentCache::in_memory().expect("Failed to create cache"); + + cache.insert("key1", vec![1.0]).await; + cache.insert("key2", vec![2.0]).await; + assert_eq!(cache.size(), 2); + + cache.clear().await; + assert_eq!(cache.size(), 0); + } +} diff --git a/crates/stratum-embeddings/src/config.rs b/crates/stratum-embeddings/src/config.rs new file mode 100644 index 0000000..dc1bf9f --- /dev/null +++ b/crates/stratum-embeddings/src/config.rs @@ -0,0 +1,153 @@ +use std::time::Duration; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EmbeddingConfig { + pub provider: ProviderConfig, + pub cache: CacheConfig, + #[serde(default)] + pub batch: BatchConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum ProviderConfig { + FastEmbed { + model: String, + }, + OpenAI { + api_key: String, + model: String, + base_url: Option, + }, + Ollama { + model: String, + base_url: Option, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CacheConfig { + pub enabled: bool, + pub max_capacity: u64, + #[serde(with = "humantime_serde")] + pub ttl: Duration, +} + +impl Default for CacheConfig { + fn default() -> Self { + Self { + enabled: true, + max_capacity: 10_000, + ttl: Duration::from_secs(3600), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BatchConfig { + pub max_concurrent: usize, + pub chunk_size: Option, +} + +impl Default for BatchConfig { + fn default() -> Self { + Self { + max_concurrent: 10, + chunk_size: None, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VectorStoreSettings { + pub dimensions: usize, + pub metric: String, +} + +impl EmbeddingConfig { + pub fn from_env(provider_type: &str) -> Result { + let provider = match provider_type { + "fastembed" => { + let model = + std::env::var("FASTEMBED_MODEL").unwrap_or_else(|_| "bge-small-en".to_string()); + ProviderConfig::FastEmbed { model } + } + "openai" => { + let api_key = std::env::var("OPENAI_API_KEY").map_err(|_| { + crate::error::EmbeddingError::ConfigError("OPENAI_API_KEY not set".to_string()) + })?; + let model = std::env::var("OPENAI_MODEL") + .unwrap_or_else(|_| "text-embedding-3-small".to_string()); + let base_url = std::env::var("OPENAI_BASE_URL").ok(); + ProviderConfig::OpenAI { + api_key, + model, + base_url, + } + } + "ollama" => { + let model = std::env::var("OLLAMA_MODEL") + .unwrap_or_else(|_| "nomic-embed-text".to_string()); + let base_url = std::env::var("OLLAMA_BASE_URL").ok(); + ProviderConfig::Ollama { model, base_url } + } + _ => { + return Err(crate::error::EmbeddingError::ConfigError(format!( + "Unknown provider type: {}", + provider_type + ))) + } + }; + + Ok(Self { + provider, + cache: CacheConfig::default(), + batch: BatchConfig::default(), + }) + } + + pub fn with_cache(mut self, config: CacheConfig) -> Self { + self.cache = config; + self + } + + pub fn with_batch(mut self, config: BatchConfig) -> Self { + self.batch = config; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_config_serialization() { + let config = EmbeddingConfig { + provider: ProviderConfig::FastEmbed { + model: "bge-small-en".to_string(), + }, + cache: CacheConfig::default(), + batch: BatchConfig::default(), + }; + + let json = serde_json::to_string(&config).expect("Failed to serialize"); + let deserialized: EmbeddingConfig = + serde_json::from_str(&json).expect("Failed to deserialize"); + + match deserialized.provider { + ProviderConfig::FastEmbed { model } => assert_eq!(model, "bge-small-en"), + _ => panic!("Wrong provider type"), + } + } + + #[test] + fn test_cache_config_defaults() { + let config = CacheConfig::default(); + assert!(config.enabled); + assert_eq!(config.max_capacity, 10_000); + assert_eq!(config.ttl, Duration::from_secs(3600)); + } +} diff --git a/crates/stratum-embeddings/src/error.rs b/crates/stratum-embeddings/src/error.rs new file mode 100644 index 0000000..eadb61d --- /dev/null +++ b/crates/stratum-embeddings/src/error.rs @@ -0,0 +1,76 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum EmbeddingError { + #[error("Provider initialization failed: {0}")] + Initialization(String), + + #[error("Provider not available: {0}")] + ProviderUnavailable(String), + + #[error("API request failed: {0}")] + ApiError(String), + + #[error("Invalid input: {0}")] + InvalidInput(String), + + #[error("Dimension mismatch: expected {expected}, got {actual}")] + DimensionMismatch { expected: usize, actual: usize }, + + #[error("Batch size {size} exceeds maximum {max}")] + BatchSizeExceeded { size: usize, max: usize }, + + #[error("Cache error: {0}")] + CacheError(String), + + #[error("Store error: {0}")] + StoreError(String), + + #[error("Serialization error: {0}")] + SerializationError(String), + + #[error("Rate limit exceeded: {0}")] + RateLimitExceeded(String), + + #[error("Timeout: {0}")] + Timeout(String), + + #[error("Configuration error: {0}")] + ConfigError(String), + + #[error("IO error: {0}")] + IoError(String), + + #[error("HTTP error: {0}")] + HttpError(String), + + #[error(transparent)] + Other(#[from] Box), +} + +impl From for EmbeddingError { + fn from(err: std::io::Error) -> Self { + Self::IoError(err.to_string()) + } +} + +impl From for EmbeddingError { + fn from(err: serde_json::Error) -> Self { + Self::SerializationError(err.to_string()) + } +} + +#[cfg(feature = "reqwest")] +impl From for EmbeddingError { + fn from(err: reqwest::Error) -> Self { + if err.is_timeout() { + Self::Timeout(err.to_string()) + } else if err.is_status() { + Self::HttpError(format!("HTTP {}: {}", err.status().unwrap(), err)) + } else { + Self::ApiError(err.to_string()) + } + } +} + +pub type Result = std::result::Result; diff --git a/crates/stratum-embeddings/src/lib.rs b/crates/stratum-embeddings/src/lib.rs new file mode 100644 index 0000000..9be6f61 --- /dev/null +++ b/crates/stratum-embeddings/src/lib.rs @@ -0,0 +1,34 @@ +pub mod batch; +pub mod cache; +pub mod config; +pub mod error; +pub mod metrics; +pub mod providers; +pub mod service; +pub mod store; +pub mod traits; + +#[cfg(feature = "memory-cache")] +pub use cache::MemoryCache; +#[cfg(feature = "persistent-cache")] +pub use cache::PersistentCache; +pub use config::{BatchConfig, CacheConfig, EmbeddingConfig, ProviderConfig}; +pub use error::{EmbeddingError, Result}; +#[cfg(feature = "cohere-provider")] +pub use providers::cohere::{CohereModel, CohereProvider}; +#[cfg(feature = "fastembed-provider")] +pub use providers::fastembed::{FastEmbedModel, FastEmbedProvider}; +#[cfg(feature = "huggingface-provider")] +pub use providers::huggingface::{HuggingFaceModel, HuggingFaceProvider}; +#[cfg(feature = "ollama-provider")] +pub use providers::ollama::{OllamaModel, OllamaProvider}; +#[cfg(feature = "openai-provider")] +pub use providers::openai::{OpenAiModel, OpenAiProvider}; +#[cfg(feature = "voyage-provider")] +pub use providers::voyage::{VoyageModel, VoyageProvider}; +pub use service::EmbeddingService; +pub use store::*; +pub use traits::{ + cosine_similarity, euclidean_distance, normalize_embedding, Embedding, EmbeddingOptions, + EmbeddingProvider, EmbeddingResult, ProviderInfo, +}; diff --git a/crates/stratum-embeddings/src/metrics.rs b/crates/stratum-embeddings/src/metrics.rs new file mode 100644 index 0000000..2e8717e --- /dev/null +++ b/crates/stratum-embeddings/src/metrics.rs @@ -0,0 +1,195 @@ +#[cfg(feature = "metrics")] +use std::sync::OnceLock; + +#[cfg(feature = "metrics")] +use prometheus::{ + register_histogram_vec, register_int_counter_vec, HistogramOpts, HistogramVec, IntCounterVec, + Opts, +}; + +#[cfg(feature = "metrics")] +static EMBEDDING_REQUESTS: OnceLock = OnceLock::new(); +#[cfg(feature = "metrics")] +static EMBEDDING_ERRORS: OnceLock = OnceLock::new(); +#[cfg(feature = "metrics")] +static EMBEDDING_DURATION: OnceLock = OnceLock::new(); +#[cfg(feature = "metrics")] +static CACHE_HITS: OnceLock = OnceLock::new(); +#[cfg(feature = "metrics")] +static CACHE_MISSES: OnceLock = OnceLock::new(); +#[cfg(feature = "metrics")] +static TOKENS_PROCESSED: OnceLock = OnceLock::new(); + +#[cfg(feature = "metrics")] +pub fn init_metrics() -> Result<(), Box> { + EMBEDDING_REQUESTS.get_or_init(|| { + register_int_counter_vec!( + Opts::new( + "embedding_requests_total", + "Total number of embedding requests" + ), + &["provider", "model"] + ) + .expect("Failed to register embedding_requests_total") + }); + + EMBEDDING_ERRORS.get_or_init(|| { + register_int_counter_vec!( + Opts::new("embedding_errors_total", "Total number of embedding errors"), + &["provider", "model", "error_type"] + ) + .expect("Failed to register embedding_errors_total") + }); + + EMBEDDING_DURATION.get_or_init(|| { + register_histogram_vec!( + HistogramOpts::new("embedding_duration_seconds", "Embedding request duration") + .buckets(vec![0.001, 0.01, 0.1, 0.5, 1.0, 5.0, 10.0]), + &["provider", "model"] + ) + .expect("Failed to register embedding_duration_seconds") + }); + + CACHE_HITS.get_or_init(|| { + register_int_counter_vec!( + Opts::new("embedding_cache_hits_total", "Total cache hits"), + &["provider", "model"] + ) + .expect("Failed to register embedding_cache_hits_total") + }); + + CACHE_MISSES.get_or_init(|| { + register_int_counter_vec!( + Opts::new("embedding_cache_misses_total", "Total cache misses"), + &["provider", "model"] + ) + .expect("Failed to register embedding_cache_misses_total") + }); + + TOKENS_PROCESSED.get_or_init(|| { + register_int_counter_vec!( + Opts::new("embedding_tokens_processed_total", "Total tokens processed"), + &["provider", "model"] + ) + .expect("Failed to register embedding_tokens_processed_total") + }); + + Ok(()) +} + +#[cfg(feature = "metrics")] +pub fn record_request(provider: &str, model: &str) { + if let Some(counter) = EMBEDDING_REQUESTS.get() { + counter.with_label_values(&[provider, model]).inc(); + } +} + +#[cfg(feature = "metrics")] +pub fn record_error(provider: &str, model: &str, error_type: &str) { + if let Some(counter) = EMBEDDING_ERRORS.get() { + counter + .with_label_values(&[provider, model, error_type]) + .inc(); + } +} + +#[cfg(feature = "metrics")] +pub fn record_duration(provider: &str, model: &str, duration_secs: f64) { + if let Some(histogram) = EMBEDDING_DURATION.get() { + histogram + .with_label_values(&[provider, model]) + .observe(duration_secs); + } +} + +#[cfg(feature = "metrics")] +pub fn record_cache_hit(provider: &str, model: &str) { + if let Some(counter) = CACHE_HITS.get() { + counter.with_label_values(&[provider, model]).inc(); + } +} + +#[cfg(feature = "metrics")] +pub fn record_cache_miss(provider: &str, model: &str) { + if let Some(counter) = CACHE_MISSES.get() { + counter.with_label_values(&[provider, model]).inc(); + } +} + +#[cfg(feature = "metrics")] +pub fn record_tokens(provider: &str, model: &str, tokens: u64) { + if let Some(counter) = TOKENS_PROCESSED.get() { + counter.with_label_values(&[provider, model]).inc_by(tokens); + } +} + +#[cfg(feature = "metrics")] +pub struct MetricsGuard { + provider: String, + model: String, + start: std::time::Instant, +} + +#[cfg(feature = "metrics")] +impl MetricsGuard { + pub fn new(provider: &str, model: &str) -> Self { + record_request(provider, model); + Self { + provider: provider.to_string(), + model: model.to_string(), + start: std::time::Instant::now(), + } + } + + pub fn record_success(self, tokens: Option) { + let duration = self.start.elapsed().as_secs_f64(); + record_duration(&self.provider, &self.model, duration); + + if let Some(token_count) = tokens { + record_tokens(&self.provider, &self.model, token_count as u64); + } + } + + pub fn record_error(self, error_type: &str) { + record_error(&self.provider, &self.model, error_type); + let duration = self.start.elapsed().as_secs_f64(); + record_duration(&self.provider, &self.model, duration); + } +} + +#[cfg(not(feature = "metrics"))] +pub fn init_metrics() -> Result<(), Box> { + Ok(()) +} + +#[cfg(test)] +#[cfg(feature = "metrics")] +mod tests { + use super::*; + + #[test] + fn test_metrics_initialization() { + let result = init_metrics(); + assert!(result.is_ok()); + } + + #[test] + fn test_record_request() { + init_metrics().unwrap(); + record_request("test-provider", "test-model"); + } + + #[test] + fn test_metrics_guard() { + init_metrics().unwrap(); + let guard = MetricsGuard::new("test", "model"); + guard.record_success(Some(100)); + } + + #[test] + fn test_cache_metrics() { + init_metrics().unwrap(); + record_cache_hit("test", "model"); + record_cache_miss("test", "model"); + } +} diff --git a/crates/stratum-embeddings/src/providers/cohere.rs b/crates/stratum-embeddings/src/providers/cohere.rs new file mode 100644 index 0000000..6e4135a --- /dev/null +++ b/crates/stratum-embeddings/src/providers/cohere.rs @@ -0,0 +1,251 @@ +#[cfg(feature = "cohere-provider")] +use async_trait::async_trait; +#[cfg(feature = "cohere-provider")] +use reqwest::Client; +#[cfg(feature = "cohere-provider")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "cohere-provider")] +use crate::{ + error::EmbeddingError, + traits::{Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum CohereModel { + #[default] + EmbedEnglishV3, + EmbedMultilingualV3, + EmbedEnglishLightV3, + EmbedMultilingualLightV3, + EmbedEnglishV2, + EmbedMultilingualV2, +} + +impl CohereModel { + pub fn model_name(&self) -> &'static str { + match self { + Self::EmbedEnglishV3 => "embed-english-v3.0", + Self::EmbedMultilingualV3 => "embed-multilingual-v3.0", + Self::EmbedEnglishLightV3 => "embed-english-light-v3.0", + Self::EmbedMultilingualLightV3 => "embed-multilingual-light-v3.0", + Self::EmbedEnglishV2 => "embed-english-v2.0", + Self::EmbedMultilingualV2 => "embed-multilingual-v2.0", + } + } + + pub fn dimensions(&self) -> usize { + match self { + Self::EmbedEnglishV3 | Self::EmbedMultilingualV3 => 1024, + Self::EmbedEnglishLightV3 | Self::EmbedMultilingualLightV3 => 384, + Self::EmbedEnglishV2 | Self::EmbedMultilingualV2 => 4096, + } + } +} + +#[derive(Debug, Serialize)] +struct CohereEmbedRequest { + model: String, + texts: Vec, + input_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + truncate: Option, +} + +#[derive(Debug, Deserialize)] +struct CohereEmbedResponse { + embeddings: Vec>, + meta: CohereMeta, +} + +#[derive(Debug, Deserialize)] +struct CohereMeta { + billed_units: Option, +} + +#[derive(Debug, Deserialize)] +struct BilledUnits { + input_tokens: Option, +} + +pub struct CohereProvider { + client: Client, + api_key: String, + model: CohereModel, + base_url: String, +} + +impl CohereProvider { + pub fn new(api_key: String, model: CohereModel) -> Self { + Self { + client: Client::new(), + api_key, + model, + base_url: "https://api.cohere.ai/v1".to_string(), + } + } + + pub fn with_base_url(mut self, base_url: String) -> Self { + self.base_url = base_url; + self + } + + pub fn embed_english_v3(api_key: String) -> Self { + Self::new(api_key, CohereModel::EmbedEnglishV3) + } + + pub fn embed_multilingual_v3(api_key: String) -> Self { + Self::new(api_key, CohereModel::EmbedMultilingualV3) + } + + async fn embed_batch_internal( + &self, + texts: &[&str], + _options: &EmbeddingOptions, + ) -> Result { + let request = CohereEmbedRequest { + model: self.model.model_name().to_string(), + texts: texts.iter().map(|s| s.to_string()).collect(), + input_type: "search_document".to_string(), + truncate: Some("END".to_string()), + }; + + let response = self + .client + .post(format!("{}/embed", self.base_url)) + .header("Authorization", format!("Bearer {}", self.api_key)) + .header("Content-Type", "application/json") + .json(&request) + .send() + .await + .map_err(|e| EmbeddingError::ApiError(format!("Cohere API request failed: {}", e)))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(EmbeddingError::ApiError(format!( + "Cohere API error {}: {}", + status, error_text + ))); + } + + let result: CohereEmbedResponse = response.json().await.map_err(|e| { + EmbeddingError::ApiError(format!("Failed to parse Cohere response: {}", e)) + })?; + + let total_tokens = result.meta.billed_units.and_then(|b| b.input_tokens); + + Ok(EmbeddingResult { + embeddings: result.embeddings, + model: self.model.model_name().to_string(), + dimensions: self.model.dimensions(), + total_tokens, + cached_count: 0, + }) + } +} + +#[async_trait] +impl EmbeddingProvider for CohereProvider { + fn name(&self) -> &str { + "cohere" + } + + fn model(&self) -> &str { + self.model.model_name() + } + + fn dimensions(&self) -> usize { + self.model.dimensions() + } + + fn is_local(&self) -> bool { + false + } + + fn max_tokens(&self) -> usize { + match self.model { + CohereModel::EmbedEnglishV3 + | CohereModel::EmbedMultilingualV3 + | CohereModel::EmbedEnglishLightV3 + | CohereModel::EmbedMultilingualLightV3 => 512, + CohereModel::EmbedEnglishV2 | CohereModel::EmbedMultilingualV2 => 512, + } + } + + fn max_batch_size(&self) -> usize { + 96 + } + + fn cost_per_1m_tokens(&self) -> f64 { + match self.model { + CohereModel::EmbedEnglishV3 | CohereModel::EmbedMultilingualV3 => 0.10, + CohereModel::EmbedEnglishLightV3 | CohereModel::EmbedMultilingualLightV3 => 0.10, + CohereModel::EmbedEnglishV2 | CohereModel::EmbedMultilingualV2 => 0.10, + } + } + + async fn is_available(&self) -> bool { + !self.api_key.is_empty() + } + + async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + let result = self.embed_batch_internal(&[text], options).await?; + result + .embeddings + .into_iter() + .next() + .ok_or_else(|| EmbeddingError::ApiError("No embedding returned".to_string())) + } + + async fn embed_batch( + &self, + texts: &[&str], + options: &EmbeddingOptions, + ) -> Result { + if texts.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Cannot embed empty text list".to_string(), + )); + } + + if texts.len() > self.max_batch_size() { + return Err(EmbeddingError::InvalidInput(format!( + "Batch size {} exceeds maximum {}", + texts.len(), + self.max_batch_size() + ))); + } + + self.embed_batch_internal(texts, options).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cohere_model_names() { + assert_eq!( + CohereModel::EmbedEnglishV3.model_name(), + "embed-english-v3.0" + ); + assert_eq!(CohereModel::EmbedMultilingualV3.dimensions(), 1024); + assert_eq!(CohereModel::EmbedEnglishLightV3.dimensions(), 384); + assert_eq!(CohereModel::EmbedEnglishV2.dimensions(), 4096); + } + + #[tokio::test] + async fn test_cohere_provider_creation() { + let provider = CohereProvider::new("test-key".to_string(), CohereModel::EmbedEnglishV3); + assert_eq!(provider.name(), "cohere"); + assert_eq!(provider.model(), "embed-english-v3.0"); + assert_eq!(provider.dimensions(), 1024); + assert_eq!(provider.max_batch_size(), 96); + } +} diff --git a/crates/stratum-embeddings/src/providers/fastembed.rs b/crates/stratum-embeddings/src/providers/fastembed.rs new file mode 100644 index 0000000..f54520d --- /dev/null +++ b/crates/stratum-embeddings/src/providers/fastembed.rs @@ -0,0 +1,247 @@ +#[cfg(feature = "fastembed-provider")] +use std::sync::{Arc, Mutex}; + +#[cfg(feature = "fastembed-provider")] +use async_trait::async_trait; +#[cfg(feature = "fastembed-provider")] +use fastembed::{EmbeddingModel, InitOptions, TextEmbedding}; + +#[cfg(feature = "fastembed-provider")] +use crate::{ + error::EmbeddingError, + traits::{ + normalize_embedding, Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult, + }, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum FastEmbedModel { + #[default] + BgeSmallEn, + BgeBaseEn, + BgeLargeEn, + AllMiniLmL6V2, + MultilingualE5Small, + MultilingualE5Base, +} + +impl FastEmbedModel { + pub fn dimensions(&self) -> usize { + match self { + Self::BgeSmallEn | Self::AllMiniLmL6V2 | Self::MultilingualE5Small => 384, + Self::BgeBaseEn | Self::MultilingualE5Base => 768, + Self::BgeLargeEn => 1024, + } + } + + pub fn model_name(&self) -> &'static str { + match self { + Self::BgeSmallEn => "BAAI/bge-small-en-v1.5", + Self::BgeBaseEn => "BAAI/bge-base-en-v1.5", + Self::BgeLargeEn => "BAAI/bge-large-en-v1.5", + Self::AllMiniLmL6V2 => "sentence-transformers/all-MiniLM-L6-v2", + Self::MultilingualE5Small => "intfloat/multilingual-e5-small", + Self::MultilingualE5Base => "intfloat/multilingual-e5-base", + } + } + + fn to_fastembed_model(self) -> EmbeddingModel { + match self { + Self::BgeSmallEn => EmbeddingModel::BGESmallENV15, + Self::BgeBaseEn => EmbeddingModel::BGEBaseENV15, + Self::BgeLargeEn => EmbeddingModel::BGELargeENV15, + Self::AllMiniLmL6V2 => EmbeddingModel::AllMiniLML6V2, + Self::MultilingualE5Small => EmbeddingModel::MultilingualE5Small, + Self::MultilingualE5Base => EmbeddingModel::MultilingualE5Base, + } + } +} + +pub struct FastEmbedProvider { + model: Arc>, + model_type: FastEmbedModel, +} + +impl FastEmbedProvider { + pub fn new(model_type: FastEmbedModel) -> Result { + let options = + InitOptions::new(model_type.to_fastembed_model()).with_show_download_progress(true); + + let model = TextEmbedding::try_new(options) + .map_err(|e| EmbeddingError::Initialization(e.to_string()))?; + + Ok(Self { + model: Arc::new(Mutex::new(model)), + model_type, + }) + } + + pub fn default_model() -> Result { + Self::new(FastEmbedModel::default()) + } + + pub fn small() -> Result { + Self::new(FastEmbedModel::BgeSmallEn) + } + + pub fn base() -> Result { + Self::new(FastEmbedModel::BgeBaseEn) + } + + pub fn large() -> Result { + Self::new(FastEmbedModel::BgeLargeEn) + } + + pub fn multilingual() -> Result { + Self::new(FastEmbedModel::MultilingualE5Base) + } +} + +#[async_trait] +impl EmbeddingProvider for FastEmbedProvider { + fn name(&self) -> &str { + "fastembed" + } + + fn model(&self) -> &str { + self.model_type.model_name() + } + + fn dimensions(&self) -> usize { + self.model_type.dimensions() + } + + fn is_local(&self) -> bool { + true + } + + fn max_tokens(&self) -> usize { + 512 + } + + fn max_batch_size(&self) -> usize { + 256 + } + + fn cost_per_1m_tokens(&self) -> f64 { + 0.0 + } + + async fn is_available(&self) -> bool { + true + } + + async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + let text = text.to_string(); + let model = Arc::clone(&self.model); + + let embeddings = tokio::task::spawn_blocking(move || { + let mut model_guard = model.lock().expect("Failed to acquire model lock"); + model_guard.embed(vec![text], None) + }) + .await + .map_err(|e| EmbeddingError::ApiError(format!("Task join error: {}", e)))? + .map_err(|e| EmbeddingError::ApiError(e.to_string()))?; + + let mut embedding = embeddings.into_iter().next().ok_or_else(|| { + EmbeddingError::ApiError("FastEmbed returned no embeddings".to_string()) + })?; + + if options.normalize { + normalize_embedding(&mut embedding); + } + + Ok(embedding) + } + + async fn embed_batch( + &self, + texts: &[&str], + options: &EmbeddingOptions, + ) -> Result { + if texts.len() > self.max_batch_size() { + return Err(EmbeddingError::BatchSizeExceeded { + size: texts.len(), + max: self.max_batch_size(), + }); + } + + let texts_owned: Vec = texts.iter().map(|s| s.to_string()).collect(); + let model = Arc::clone(&self.model); + + let mut embeddings = tokio::task::spawn_blocking(move || { + let mut model_guard = model.lock().expect("Failed to acquire model lock"); + model_guard.embed(texts_owned, None) + }) + .await + .map_err(|e| EmbeddingError::ApiError(format!("Task join error: {}", e)))? + .map_err(|e| EmbeddingError::ApiError(e.to_string()))?; + + if options.normalize { + embeddings.iter_mut().for_each(normalize_embedding); + } + + Ok(EmbeddingResult { + embeddings, + model: self.model().to_string(), + dimensions: self.dimensions(), + total_tokens: None, + cached_count: 0, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_fastembed_provider() { + let provider = FastEmbedProvider::small().expect("Failed to initialize FastEmbed"); + + assert_eq!(provider.name(), "fastembed"); + assert_eq!(provider.dimensions(), 384); + assert!(provider.is_local()); + assert_eq!(provider.cost_per_1m_tokens(), 0.0); + + let options = EmbeddingOptions::default_with_cache(); + let embedding = provider + .embed("Hello world", &options) + .await + .expect("Failed to embed"); + + assert_eq!(embedding.len(), 384); + + let magnitude: f32 = embedding.iter().map(|x| x * x).sum::().sqrt(); + assert!( + (magnitude - 1.0).abs() < 0.01, + "Embedding should be normalized" + ); + } + + #[tokio::test] + async fn test_fastembed_batch() { + let provider = FastEmbedProvider::small().expect("Failed to initialize FastEmbed"); + + let texts = vec!["Hello world", "Goodbye world", "Machine learning"]; + let options = EmbeddingOptions::default_with_cache(); + + let result = provider + .embed_batch(&texts, &options) + .await + .expect("Failed to embed batch"); + + assert_eq!(result.embeddings.len(), 3); + assert_eq!(result.dimensions, 384); + + for embedding in &result.embeddings { + assert_eq!(embedding.len(), 384); + let magnitude: f32 = embedding.iter().map(|x| x * x).sum::().sqrt(); + assert!((magnitude - 1.0).abs() < 0.01); + } + } +} diff --git a/crates/stratum-embeddings/src/providers/huggingface.rs b/crates/stratum-embeddings/src/providers/huggingface.rs new file mode 100644 index 0000000..fff8235 --- /dev/null +++ b/crates/stratum-embeddings/src/providers/huggingface.rs @@ -0,0 +1,344 @@ +#[cfg(feature = "huggingface-provider")] +use std::time::Duration; + +#[cfg(feature = "huggingface-provider")] +use async_trait::async_trait; +#[cfg(feature = "huggingface-provider")] +use reqwest::Client; +#[cfg(feature = "huggingface-provider")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "huggingface-provider")] +use crate::{ + error::EmbeddingError, + traits::{ + normalize_embedding, Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult, + }, +}; + +#[derive(Debug, Clone, PartialEq)] +pub enum HuggingFaceModel { + /// BAAI/bge-small-en-v1.5 - 384 dimensions, efficient general-purpose + BgeSmall, + /// BAAI/bge-base-en-v1.5 - 768 dimensions, balanced performance + BgeBase, + /// BAAI/bge-large-en-v1.5 - 1024 dimensions, high quality + BgeLarge, + /// sentence-transformers/all-MiniLM-L6-v2 - 384 dimensions, fast + AllMiniLm, + /// sentence-transformers/all-mpnet-base-v2 - 768 dimensions, strong baseline + AllMpnet, + /// Custom model with model ID and dimensions + Custom(String, usize), +} + +impl Default for HuggingFaceModel { + fn default() -> Self { + Self::BgeSmall + } +} + +impl HuggingFaceModel { + pub fn model_id(&self) -> &str { + match self { + Self::BgeSmall => "BAAI/bge-small-en-v1.5", + Self::BgeBase => "BAAI/bge-base-en-v1.5", + Self::BgeLarge => "BAAI/bge-large-en-v1.5", + Self::AllMiniLm => "sentence-transformers/all-MiniLM-L6-v2", + Self::AllMpnet => "sentence-transformers/all-mpnet-base-v2", + Self::Custom(id, _) => id.as_str(), + } + } + + pub fn dimensions(&self) -> usize { + match self { + Self::BgeSmall => 384, + Self::BgeBase => 768, + Self::BgeLarge => 1024, + Self::AllMiniLm => 384, + Self::AllMpnet => 768, + Self::Custom(_, dims) => *dims, + } + } + + pub fn from_model_id(id: &str, dimensions: Option) -> Self { + match id { + "BAAI/bge-small-en-v1.5" => Self::BgeSmall, + "BAAI/bge-base-en-v1.5" => Self::BgeBase, + "BAAI/bge-large-en-v1.5" => Self::BgeLarge, + "sentence-transformers/all-MiniLM-L6-v2" => Self::AllMiniLm, + "sentence-transformers/all-mpnet-base-v2" => Self::AllMpnet, + _ => Self::Custom( + id.to_string(), + dimensions.unwrap_or(384), // Default to 384 if unknown + ), + } + } +} + +#[cfg(feature = "huggingface-provider")] +pub struct HuggingFaceProvider { + client: Client, + api_key: String, + model: HuggingFaceModel, +} + +#[cfg(feature = "huggingface-provider")] +#[derive(Debug, Serialize)] +struct HFRequest { + inputs: String, +} + +#[cfg(feature = "huggingface-provider")] +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum HFResponse { + /// Single text embedding response + Single(Vec), + /// Batch embedding response + Multiple(Vec>), +} + +#[cfg(feature = "huggingface-provider")] +impl HuggingFaceProvider { + const BASE_URL: &'static str = "https://api-inference.huggingface.co/pipeline/feature-extraction"; + + pub fn new(api_key: impl Into, model: HuggingFaceModel) -> Result { + let api_key = api_key.into(); + if api_key.is_empty() { + return Err(EmbeddingError::ConfigError( + "HuggingFace API key is empty".to_string(), + )); + } + + let client = Client::builder() + .timeout(Duration::from_secs(120)) + .build() + .map_err(|e| EmbeddingError::Initialization(e.to_string()))?; + + Ok(Self { + client, + api_key, + model, + }) + } + + pub fn from_env(model: HuggingFaceModel) -> Result { + let api_key = std::env::var("HUGGINGFACE_API_KEY") + .or_else(|_| std::env::var("HF_TOKEN")) + .map_err(|_| { + EmbeddingError::ConfigError( + "HUGGINGFACE_API_KEY or HF_TOKEN environment variable not set".to_string(), + ) + })?; + Self::new(api_key, model) + } + + pub fn bge_small() -> Result { + Self::from_env(HuggingFaceModel::BgeSmall) + } + + pub fn bge_base() -> Result { + Self::from_env(HuggingFaceModel::BgeBase) + } + + pub fn all_minilm() -> Result { + Self::from_env(HuggingFaceModel::AllMiniLm) + } +} + +#[cfg(feature = "huggingface-provider")] +#[async_trait] +impl EmbeddingProvider for HuggingFaceProvider { + fn name(&self) -> &str { + "huggingface" + } + + fn model(&self) -> &str { + self.model.model_id() + } + + fn dimensions(&self) -> usize { + self.model.dimensions() + } + + fn is_local(&self) -> bool { + false + } + + fn max_tokens(&self) -> usize { + // HuggingFace doesn't specify a hard limit, but most models handle ~512 tokens + 512 + } + + fn max_batch_size(&self) -> usize { + // HuggingFace Inference API doesn't support batch requests + // Each request is individual + 1 + } + + fn cost_per_1m_tokens(&self) -> f64 { + // HuggingFace Inference API is free for public models + // For dedicated endpoints, costs vary + 0.0 + } + + async fn is_available(&self) -> bool { + // HuggingFace Inference API is always available (with rate limits) + true + } + + async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + if text.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Text cannot be empty".to_string(), + )); + } + + let url = format!("{}/{}", Self::BASE_URL, self.model.model_id()); + let request = HFRequest { + inputs: text.to_string(), + }; + + let response = self + .client + .post(&url) + .header("Authorization", format!("Bearer {}", self.api_key)) + .json(&request) + .send() + .await + .map_err(|e| { + EmbeddingError::ApiError(format!("HuggingFace API request failed: {}", e)) + })?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(EmbeddingError::ApiError(format!( + "HuggingFace API error {}: {}", + status, error_text + ))); + } + + let hf_response: HFResponse = response.json().await.map_err(|e| { + EmbeddingError::ApiError(format!("Failed to parse HuggingFace response: {}", e)) + })?; + + let mut embedding = match hf_response { + HFResponse::Single(emb) => emb, + HFResponse::Multiple(embs) => { + if embs.is_empty() { + return Err(EmbeddingError::ApiError( + "Empty embeddings response from HuggingFace".to_string(), + )); + } + embs[0].clone() + } + }; + + // Validate dimensions + if embedding.len() != self.dimensions() { + return Err(EmbeddingError::DimensionMismatch { + expected: self.dimensions(), + actual: embedding.len(), + }); + } + + // Normalize if requested + if options.normalize { + normalize_embedding(&mut embedding); + } + + Ok(embedding) + } + + async fn embed_batch( + &self, + texts: &[&str], + options: &EmbeddingOptions, + ) -> Result { + if texts.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Texts cannot be empty".to_string(), + )); + } + + // HuggingFace Inference API doesn't support true batch requests + // We need to send individual requests + let mut embeddings = Vec::with_capacity(texts.len()); + + for text in texts { + let embedding = self.embed(text, options).await?; + embeddings.push(embedding); + } + + Ok(EmbeddingResult { + embeddings, + model: self.model().to_string(), + dimensions: self.dimensions(), + total_tokens: None, + cached_count: 0, + }) + } +} + +#[cfg(all(test, feature = "huggingface-provider"))] +mod tests { + use super::*; + + #[test] + fn test_model_id_mapping() { + assert_eq!( + HuggingFaceModel::BgeSmall.model_id(), + "BAAI/bge-small-en-v1.5" + ); + assert_eq!(HuggingFaceModel::BgeSmall.dimensions(), 384); + + assert_eq!( + HuggingFaceModel::BgeBase.model_id(), + "BAAI/bge-base-en-v1.5" + ); + assert_eq!(HuggingFaceModel::BgeBase.dimensions(), 768); + } + + #[test] + fn test_custom_model() { + let custom = HuggingFaceModel::Custom("my-model".to_string(), 512); + assert_eq!(custom.model_id(), "my-model"); + assert_eq!(custom.dimensions(), 512); + } + + #[test] + fn test_from_model_id() { + let model = HuggingFaceModel::from_model_id("BAAI/bge-small-en-v1.5", None); + assert_eq!(model, HuggingFaceModel::BgeSmall); + + let custom = HuggingFaceModel::from_model_id("unknown-model", Some(256)); + assert!(matches!(custom, HuggingFaceModel::Custom(_, 256))); + } + + #[test] + fn test_provider_creation() { + let provider = HuggingFaceProvider::new("test-key", HuggingFaceModel::BgeSmall); + assert!(provider.is_ok()); + + let provider = provider.unwrap(); + assert_eq!(provider.name(), "huggingface"); + assert_eq!(provider.model(), "BAAI/bge-small-en-v1.5"); + assert_eq!(provider.dimensions(), 384); + } + + #[test] + fn test_empty_api_key() { + let provider = HuggingFaceProvider::new("", HuggingFaceModel::BgeSmall); + assert!(provider.is_err()); + assert!(matches!( + provider.unwrap_err(), + EmbeddingError::ConfigError(_) + )); + } +} diff --git a/crates/stratum-embeddings/src/providers/mod.rs b/crates/stratum-embeddings/src/providers/mod.rs new file mode 100644 index 0000000..192c8f1 --- /dev/null +++ b/crates/stratum-embeddings/src/providers/mod.rs @@ -0,0 +1,25 @@ +#[cfg(feature = "cohere-provider")] +pub mod cohere; +#[cfg(feature = "fastembed-provider")] +pub mod fastembed; +#[cfg(feature = "huggingface-provider")] +pub mod huggingface; +#[cfg(feature = "ollama-provider")] +pub mod ollama; +#[cfg(feature = "openai-provider")] +pub mod openai; +#[cfg(feature = "voyage-provider")] +pub mod voyage; + +#[cfg(feature = "cohere-provider")] +pub use cohere::{CohereModel, CohereProvider}; +#[cfg(feature = "fastembed-provider")] +pub use fastembed::{FastEmbedModel, FastEmbedProvider}; +#[cfg(feature = "huggingface-provider")] +pub use huggingface::{HuggingFaceModel, HuggingFaceProvider}; +#[cfg(feature = "ollama-provider")] +pub use ollama::{OllamaModel, OllamaProvider}; +#[cfg(feature = "openai-provider")] +pub use openai::{OpenAiModel, OpenAiProvider}; +#[cfg(feature = "voyage-provider")] +pub use voyage::{VoyageModel, VoyageProvider}; diff --git a/crates/stratum-embeddings/src/providers/ollama.rs b/crates/stratum-embeddings/src/providers/ollama.rs new file mode 100644 index 0000000..938c8cc --- /dev/null +++ b/crates/stratum-embeddings/src/providers/ollama.rs @@ -0,0 +1,272 @@ +#[cfg(feature = "ollama-provider")] +use std::time::Duration; + +#[cfg(feature = "ollama-provider")] +use async_trait::async_trait; +#[cfg(feature = "ollama-provider")] +use reqwest::Client; +#[cfg(feature = "ollama-provider")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "ollama-provider")] +use crate::{ + error::EmbeddingError, + traits::{ + normalize_embedding, Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult, + }, +}; + +#[derive(Debug, Clone, PartialEq, Default)] +pub enum OllamaModel { + #[default] + NomicEmbed, + MxbaiEmbed, + AllMiniLm, + Custom(String, usize), +} + +impl OllamaModel { + pub fn model_name(&self) -> &str { + match self { + Self::NomicEmbed => "nomic-embed-text", + Self::MxbaiEmbed => "mxbai-embed-large", + Self::AllMiniLm => "all-minilm", + Self::Custom(name, _) => name, + } + } + + pub fn dimensions(&self) -> usize { + match self { + Self::NomicEmbed => 768, + Self::MxbaiEmbed => 1024, + Self::AllMiniLm => 384, + Self::Custom(_, dims) => *dims, + } + } +} + +#[derive(Serialize)] +struct OllamaRequest { + model: String, + input: OllamaInput, + #[serde(skip_serializing_if = "Option::is_none")] + options: Option, +} + +#[derive(Serialize)] +#[serde(untagged)] +enum OllamaInput { + Single(String), + Batch(Vec), +} + +#[derive(Deserialize)] +struct OllamaResponse { + #[serde(default)] + embeddings: Vec>, + #[serde(default)] + embedding: Option>, +} + +pub struct OllamaProvider { + client: Client, + model: OllamaModel, + base_url: String, +} + +impl OllamaProvider { + pub fn new(model: OllamaModel) -> Result { + let client = Client::builder() + .timeout(Duration::from_secs(120)) + .build() + .map_err(|e| EmbeddingError::Initialization(e.to_string()))?; + + Ok(Self { + client, + model, + base_url: "http://localhost:11434".to_string(), + }) + } + + pub fn default_model() -> Result { + Self::new(OllamaModel::default()) + } + + pub fn with_base_url(mut self, base_url: impl Into) -> Self { + self.base_url = base_url.into(); + self + } + + pub fn custom_model( + name: impl Into, + dimensions: usize, + ) -> Result { + Self::new(OllamaModel::Custom(name.into(), dimensions)) + } + + async fn call_api(&self, input: OllamaInput) -> Result { + let request = OllamaRequest { + model: self.model.model_name().to_string(), + input, + options: None, + }; + + let response = self + .client + .post(format!("{}/api/embed", self.base_url)) + .json(&request) + .send() + .await + .map_err(|e| { + if e.is_connect() { + EmbeddingError::ProviderUnavailable(format!( + "Cannot connect to Ollama at {}", + self.base_url + )) + } else { + e.into() + } + })?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response + .text() + .await + .unwrap_or_else(|_| "Unknown error".to_string()); + return Err(EmbeddingError::ApiError(format!( + "Ollama API error {}: {}", + status, error_text + ))); + } + + let api_response: OllamaResponse = response.json().await?; + Ok(api_response) + } +} + +#[async_trait] +impl EmbeddingProvider for OllamaProvider { + fn name(&self) -> &str { + "ollama" + } + + fn model(&self) -> &str { + self.model.model_name() + } + + fn dimensions(&self) -> usize { + self.model.dimensions() + } + + fn is_local(&self) -> bool { + true + } + + fn max_tokens(&self) -> usize { + 2048 + } + + fn max_batch_size(&self) -> usize { + 128 + } + + fn cost_per_1m_tokens(&self) -> f64 { + 0.0 + } + + async fn is_available(&self) -> bool { + self.client + .get(format!("{}/api/tags", self.base_url)) + .send() + .await + .map(|r| r.status().is_success()) + .unwrap_or(false) + } + + async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + if text.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Text cannot be empty".to_string(), + )); + } + + let response = self.call_api(OllamaInput::Single(text.to_string())).await?; + + let mut embedding = if let Some(emb) = response.embedding { + emb + } else { + response.embeddings.into_iter().next().ok_or_else(|| { + EmbeddingError::ApiError("Ollama returned no embeddings".to_string()) + })? + }; + + if options.normalize { + normalize_embedding(&mut embedding); + } + + Ok(embedding) + } + + async fn embed_batch( + &self, + texts: &[&str], + options: &EmbeddingOptions, + ) -> Result { + if texts.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Texts cannot be empty".to_string(), + )); + } + + if texts.len() > self.max_batch_size() { + return Err(EmbeddingError::BatchSizeExceeded { + size: texts.len(), + max: self.max_batch_size(), + }); + } + + let texts_owned: Vec = texts.iter().map(|s| s.to_string()).collect(); + let response = self.call_api(OllamaInput::Batch(texts_owned)).await?; + + let mut embeddings = response.embeddings; + + if embeddings.is_empty() { + return Err(EmbeddingError::ApiError( + "Ollama returned no embeddings".to_string(), + )); + } + + if options.normalize { + embeddings.iter_mut().for_each(normalize_embedding); + } + + Ok(EmbeddingResult { + embeddings, + model: self.model().to_string(), + dimensions: self.dimensions(), + total_tokens: None, + cached_count: 0, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ollama_model_metadata() { + assert_eq!(OllamaModel::NomicEmbed.dimensions(), 768); + assert_eq!(OllamaModel::MxbaiEmbed.dimensions(), 1024); + assert_eq!(OllamaModel::AllMiniLm.dimensions(), 384); + + let custom = OllamaModel::Custom("my-model".to_string(), 512); + assert_eq!(custom.model_name(), "my-model"); + assert_eq!(custom.dimensions(), 512); + } +} diff --git a/crates/stratum-embeddings/src/providers/openai.rs b/crates/stratum-embeddings/src/providers/openai.rs new file mode 100644 index 0000000..92ea8b7 --- /dev/null +++ b/crates/stratum-embeddings/src/providers/openai.rs @@ -0,0 +1,286 @@ +#[cfg(feature = "openai-provider")] +use std::time::Duration; + +#[cfg(feature = "openai-provider")] +use async_trait::async_trait; +#[cfg(feature = "openai-provider")] +use reqwest::Client; +#[cfg(feature = "openai-provider")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "openai-provider")] +use crate::{ + error::EmbeddingError, + traits::{ + normalize_embedding, Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult, + }, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum OpenAiModel { + #[default] + TextEmbedding3Small, + TextEmbedding3Large, + TextEmbeddingAda002, +} + +impl OpenAiModel { + pub fn model_name(&self) -> &'static str { + match self { + Self::TextEmbedding3Small => "text-embedding-3-small", + Self::TextEmbedding3Large => "text-embedding-3-large", + Self::TextEmbeddingAda002 => "text-embedding-ada-002", + } + } + + pub fn dimensions(&self) -> usize { + match self { + Self::TextEmbedding3Small => 1536, + Self::TextEmbedding3Large => 3072, + Self::TextEmbeddingAda002 => 1536, + } + } + + pub fn cost_per_1m(&self) -> f64 { + match self { + Self::TextEmbedding3Small => 0.02, + Self::TextEmbedding3Large => 0.13, + Self::TextEmbeddingAda002 => 0.10, + } + } +} + +#[derive(Serialize)] +struct OpenAiRequest { + input: OpenAiInput, + model: String, + #[serde(skip_serializing_if = "Option::is_none")] + encoding_format: Option, +} + +#[derive(Serialize)] +#[serde(untagged)] +enum OpenAiInput { + Single(String), + Batch(Vec), +} + +#[derive(Deserialize)] +struct OpenAiResponse { + data: Vec, + usage: OpenAiUsage, +} + +#[derive(Deserialize)] +struct OpenAiEmbedding { + embedding: Vec, + index: usize, +} + +#[derive(Deserialize)] +struct OpenAiUsage { + total_tokens: u32, +} + +pub struct OpenAiProvider { + client: Client, + api_key: String, + model: OpenAiModel, + base_url: String, +} + +impl OpenAiProvider { + pub fn new(api_key: impl Into, model: OpenAiModel) -> Result { + let api_key = api_key.into(); + if api_key.is_empty() { + return Err(EmbeddingError::ConfigError( + "OpenAI API key is empty".to_string(), + )); + } + + let client = Client::builder() + .timeout(Duration::from_secs(60)) + .build() + .map_err(|e| EmbeddingError::Initialization(e.to_string()))?; + + Ok(Self { + client, + api_key, + model, + base_url: "https://api.openai.com/v1".to_string(), + }) + } + + pub fn from_env(model: OpenAiModel) -> Result { + let api_key = std::env::var("OPENAI_API_KEY") + .map_err(|_| EmbeddingError::ConfigError("OPENAI_API_KEY not set".to_string()))?; + Self::new(api_key, model) + } + + pub fn with_base_url(mut self, base_url: impl Into) -> Self { + self.base_url = base_url.into(); + self + } + + async fn call_api(&self, input: OpenAiInput) -> Result { + let request = OpenAiRequest { + input, + model: self.model.model_name().to_string(), + encoding_format: None, + }; + + let response = self + .client + .post(format!("{}/embeddings", self.base_url)) + .header("Authorization", format!("Bearer {}", self.api_key)) + .header("Content-Type", "application/json") + .json(&request) + .send() + .await?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response + .text() + .await + .unwrap_or_else(|_| "Unknown error".to_string()); + return Err(EmbeddingError::ApiError(format!( + "OpenAI API error {}: {}", + status, error_text + ))); + } + + let api_response: OpenAiResponse = response.json().await?; + Ok(api_response) + } +} + +#[async_trait] +impl EmbeddingProvider for OpenAiProvider { + fn name(&self) -> &str { + "openai" + } + + fn model(&self) -> &str { + self.model.model_name() + } + + fn dimensions(&self) -> usize { + self.model.dimensions() + } + + fn is_local(&self) -> bool { + false + } + + fn max_tokens(&self) -> usize { + 8191 + } + + fn max_batch_size(&self) -> usize { + 2048 + } + + fn cost_per_1m_tokens(&self) -> f64 { + self.model.cost_per_1m() + } + + async fn is_available(&self) -> bool { + self.client + .get(format!("{}/models", self.base_url)) + .header("Authorization", format!("Bearer {}", self.api_key)) + .send() + .await + .map(|r| r.status().is_success()) + .unwrap_or(false) + } + + async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + if text.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Text cannot be empty".to_string(), + )); + } + + let response = self.call_api(OpenAiInput::Single(text.to_string())).await?; + + let mut embedding = response + .data + .into_iter() + .next() + .ok_or_else(|| EmbeddingError::ApiError("OpenAI returned no embeddings".to_string()))? + .embedding; + + if options.normalize { + normalize_embedding(&mut embedding); + } + + Ok(embedding) + } + + async fn embed_batch( + &self, + texts: &[&str], + options: &EmbeddingOptions, + ) -> Result { + if texts.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Texts cannot be empty".to_string(), + )); + } + + if texts.len() > self.max_batch_size() { + return Err(EmbeddingError::BatchSizeExceeded { + size: texts.len(), + max: self.max_batch_size(), + }); + } + + let texts_owned: Vec = texts.iter().map(|s| s.to_string()).collect(); + let response = self.call_api(OpenAiInput::Batch(texts_owned)).await?; + + let mut embeddings_with_index: Vec<_> = response + .data + .into_iter() + .map(|e| (e.index, e.embedding)) + .collect(); + + embeddings_with_index.sort_by_key(|(idx, _)| *idx); + + let mut embeddings: Vec = embeddings_with_index + .into_iter() + .map(|(_, emb)| emb) + .collect(); + + if options.normalize { + embeddings.iter_mut().for_each(normalize_embedding); + } + + Ok(EmbeddingResult { + embeddings, + model: self.model().to_string(), + dimensions: self.dimensions(), + total_tokens: Some(response.usage.total_tokens), + cached_count: 0, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_openai_model_metadata() { + assert_eq!(OpenAiModel::TextEmbedding3Small.dimensions(), 1536); + assert_eq!(OpenAiModel::TextEmbedding3Large.dimensions(), 3072); + assert_eq!(OpenAiModel::TextEmbeddingAda002.dimensions(), 1536); + + assert_eq!(OpenAiModel::TextEmbedding3Small.cost_per_1m(), 0.02); + assert_eq!(OpenAiModel::TextEmbedding3Large.cost_per_1m(), 0.13); + } +} diff --git a/crates/stratum-embeddings/src/providers/voyage.rs b/crates/stratum-embeddings/src/providers/voyage.rs new file mode 100644 index 0000000..ea61076 --- /dev/null +++ b/crates/stratum-embeddings/src/providers/voyage.rs @@ -0,0 +1,259 @@ +#[cfg(feature = "voyage-provider")] +use async_trait::async_trait; +#[cfg(feature = "voyage-provider")] +use reqwest::Client; +#[cfg(feature = "voyage-provider")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "voyage-provider")] +use crate::{ + error::EmbeddingError, + traits::{Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum VoyageModel { + #[default] + Voyage2, + VoyageLarge2, + VoyageCode2, + VoyageLite02Instruct, +} + +impl VoyageModel { + pub fn model_name(&self) -> &'static str { + match self { + Self::Voyage2 => "voyage-2", + Self::VoyageLarge2 => "voyage-large-2", + Self::VoyageCode2 => "voyage-code-2", + Self::VoyageLite02Instruct => "voyage-lite-02-instruct", + } + } + + pub fn dimensions(&self) -> usize { + match self { + Self::Voyage2 => 1024, + Self::VoyageLarge2 => 1536, + Self::VoyageCode2 => 1536, + Self::VoyageLite02Instruct => 1024, + } + } + + pub fn max_tokens(&self) -> usize { + match self { + Self::Voyage2 | Self::VoyageLarge2 | Self::VoyageCode2 => 16000, + Self::VoyageLite02Instruct => 4000, + } + } +} + +#[derive(Debug, Serialize)] +struct VoyageEmbedRequest { + input: Vec, + model: String, + #[serde(skip_serializing_if = "Option::is_none")] + input_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + truncation: Option, +} + +#[derive(Debug, Deserialize)] +struct VoyageEmbedResponse { + data: Vec, + usage: VoyageUsage, +} + +#[derive(Debug, Deserialize)] +struct VoyageEmbeddingData { + embedding: Vec, +} + +#[derive(Debug, Deserialize)] +struct VoyageUsage { + total_tokens: u32, +} + +pub struct VoyageProvider { + client: Client, + api_key: String, + model: VoyageModel, + base_url: String, +} + +impl VoyageProvider { + pub fn new(api_key: String, model: VoyageModel) -> Self { + Self { + client: Client::new(), + api_key, + model, + base_url: "https://api.voyageai.com/v1".to_string(), + } + } + + pub fn with_base_url(mut self, base_url: String) -> Self { + self.base_url = base_url; + self + } + + pub fn voyage_2(api_key: String) -> Self { + Self::new(api_key, VoyageModel::Voyage2) + } + + pub fn voyage_large_2(api_key: String) -> Self { + Self::new(api_key, VoyageModel::VoyageLarge2) + } + + pub fn voyage_code_2(api_key: String) -> Self { + Self::new(api_key, VoyageModel::VoyageCode2) + } + + async fn embed_batch_internal( + &self, + texts: &[&str], + _options: &EmbeddingOptions, + ) -> Result { + let request = VoyageEmbedRequest { + input: texts.iter().map(|s| s.to_string()).collect(), + model: self.model.model_name().to_string(), + input_type: Some("document".to_string()), + truncation: Some(true), + }; + + let response = self + .client + .post(format!("{}/embeddings", self.base_url)) + .header("Authorization", format!("Bearer {}", self.api_key)) + .header("Content-Type", "application/json") + .json(&request) + .send() + .await + .map_err(|e| EmbeddingError::ApiError(format!("Voyage API request failed: {}", e)))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(EmbeddingError::ApiError(format!( + "Voyage API error {}: {}", + status, error_text + ))); + } + + let result: VoyageEmbedResponse = response.json().await.map_err(|e| { + EmbeddingError::ApiError(format!("Failed to parse Voyage response: {}", e)) + })?; + + let embeddings: Vec = result.data.into_iter().map(|d| d.embedding).collect(); + + Ok(EmbeddingResult { + embeddings, + model: self.model.model_name().to_string(), + dimensions: self.model.dimensions(), + total_tokens: Some(result.usage.total_tokens), + cached_count: 0, + }) + } +} + +#[async_trait] +impl EmbeddingProvider for VoyageProvider { + fn name(&self) -> &str { + "voyage" + } + + fn model(&self) -> &str { + self.model.model_name() + } + + fn dimensions(&self) -> usize { + self.model.dimensions() + } + + fn is_local(&self) -> bool { + false + } + + fn max_tokens(&self) -> usize { + self.model.max_tokens() + } + + fn max_batch_size(&self) -> usize { + 128 + } + + fn cost_per_1m_tokens(&self) -> f64 { + match self.model { + VoyageModel::Voyage2 => 0.10, + VoyageModel::VoyageLarge2 => 0.12, + VoyageModel::VoyageCode2 => 0.12, + VoyageModel::VoyageLite02Instruct => 0.06, + } + } + + async fn is_available(&self) -> bool { + !self.api_key.is_empty() + } + + async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + let result = self.embed_batch_internal(&[text], options).await?; + result + .embeddings + .into_iter() + .next() + .ok_or_else(|| EmbeddingError::ApiError("No embedding returned".to_string())) + } + + async fn embed_batch( + &self, + texts: &[&str], + options: &EmbeddingOptions, + ) -> Result { + if texts.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Cannot embed empty text list".to_string(), + )); + } + + if texts.len() > self.max_batch_size() { + return Err(EmbeddingError::InvalidInput(format!( + "Batch size {} exceeds maximum {}", + texts.len(), + self.max_batch_size() + ))); + } + + self.embed_batch_internal(texts, options).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_voyage_model_names() { + assert_eq!(VoyageModel::Voyage2.model_name(), "voyage-2"); + assert_eq!(VoyageModel::Voyage2.dimensions(), 1024); + assert_eq!(VoyageModel::VoyageLarge2.dimensions(), 1536); + assert_eq!(VoyageModel::VoyageCode2.dimensions(), 1536); + assert_eq!(VoyageModel::VoyageLite02Instruct.dimensions(), 1024); + } + + #[test] + fn test_voyage_max_tokens() { + assert_eq!(VoyageModel::Voyage2.max_tokens(), 16000); + assert_eq!(VoyageModel::VoyageLite02Instruct.max_tokens(), 4000); + } + + #[tokio::test] + async fn test_voyage_provider_creation() { + let provider = VoyageProvider::new("test-key".to_string(), VoyageModel::Voyage2); + assert_eq!(provider.name(), "voyage"); + assert_eq!(provider.model(), "voyage-2"); + assert_eq!(provider.dimensions(), 1024); + assert_eq!(provider.max_batch_size(), 128); + } +} diff --git a/crates/stratum-embeddings/src/service.rs b/crates/stratum-embeddings/src/service.rs new file mode 100644 index 0000000..ae48d21 --- /dev/null +++ b/crates/stratum-embeddings/src/service.rs @@ -0,0 +1,283 @@ +use std::sync::Arc; + +use tracing::{debug, info, warn}; + +use crate::{ + batch::BatchProcessor, + cache::EmbeddingCache, + error::EmbeddingError, + traits::{Embedding, EmbeddingOptions, EmbeddingProvider, EmbeddingResult, ProviderInfo}, +}; + +pub struct EmbeddingService { + provider: Arc

, + cache: Option>, + fallback_providers: Vec>, + batch_processor: BatchProcessor, +} + +impl EmbeddingService { + pub fn new(provider: P) -> Self { + let provider = Arc::new(provider); + let batch_processor = BatchProcessor::new(Arc::clone(&provider), None); + + Self { + provider, + cache: None, + fallback_providers: Vec::new(), + batch_processor, + } + } + + pub fn with_cache(mut self, cache: C) -> Self { + let cache = Arc::new(cache); + self.cache = Some(Arc::clone(&cache)); + self.batch_processor = BatchProcessor::new(Arc::clone(&self.provider), Some(cache)); + self + } + + pub fn with_fallback(mut self, fallback: Arc) -> Self { + self.fallback_providers.push(fallback); + self + } + + pub fn with_batch_concurrency(mut self, max_concurrent: usize) -> Self { + self.batch_processor = self.batch_processor.with_concurrency(max_concurrent); + self + } + + pub fn provider_info(&self) -> ProviderInfo { + ProviderInfo::from(self.provider.as_ref()) + } + + pub async fn is_ready(&self) -> bool { + self.provider.is_available().await + } + + pub async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + if text.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Text cannot be empty".to_string(), + )); + } + + if options.use_cache { + if let Some(cache) = &self.cache { + let key = + crate::cache::cache_key(self.provider.name(), self.provider.model(), text); + if let Some(cached) = cache.get(&key).await { + debug!("Cache hit for text (len={})", text.len()); + return Ok(cached); + } + } + } + + let result = self.embed_with_fallback(text, options).await?; + + if options.use_cache { + if let Some(cache) = &self.cache { + let key = + crate::cache::cache_key(self.provider.name(), self.provider.model(), text); + cache.insert(&key, result.clone()).await; + } + } + + Ok(result) + } + + pub async fn embed_batch( + &self, + texts: Vec, + options: &EmbeddingOptions, + ) -> Result { + if texts.is_empty() { + return Err(EmbeddingError::InvalidInput( + "Texts cannot be empty".to_string(), + )); + } + + info!("Processing batch of {} texts", texts.len()); + self.batch_processor.process_batch(texts, options).await + } + + pub async fn embed_stream( + &self, + texts: Vec, + options: &EmbeddingOptions, + ) -> Result, EmbeddingError> { + self.batch_processor.process_stream(texts, options).await + } + + async fn embed_with_fallback( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result { + match self.provider.embed(text, options).await { + Ok(embedding) => Ok(embedding), + Err(e) => { + warn!("Primary provider failed: {}", e); + + for (idx, fallback) in self.fallback_providers.iter().enumerate() { + debug!("Trying fallback provider {}", idx); + + if !fallback.is_available().await { + warn!("Fallback provider {} not available", idx); + continue; + } + + match fallback.embed(text, options).await { + Ok(embedding) => { + info!("Fallback provider {} succeeded", idx); + return Ok(embedding); + } + Err(fallback_err) => { + warn!("Fallback provider {} failed: {}", idx, fallback_err); + } + } + } + + Err(e) + } + } + } + + pub async fn invalidate_cache(&self, text: &str) -> Result<(), EmbeddingError> { + if let Some(cache) = &self.cache { + let key = crate::cache::cache_key(self.provider.name(), self.provider.model(), text); + cache.invalidate(&key).await; + Ok(()) + } else { + Err(EmbeddingError::CacheError( + "No cache configured".to_string(), + )) + } + } + + pub async fn clear_cache(&self) -> Result<(), EmbeddingError> { + if let Some(cache) = &self.cache { + cache.clear().await; + Ok(()) + } else { + Err(EmbeddingError::CacheError( + "No cache configured".to_string(), + )) + } + } + + pub fn cache_size(&self) -> usize { + self.cache.as_ref().map(|c| c.size()).unwrap_or(0) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::*; + use crate::{cache::MemoryCache, providers::FastEmbedProvider}; + + #[tokio::test] + async fn test_service_basic() { + let provider = FastEmbedProvider::small().expect("Failed to init"); + let service: EmbeddingService<_, MemoryCache> = EmbeddingService::new(provider); + + let options = EmbeddingOptions::no_cache(); + let embedding = service + .embed("Hello world", &options) + .await + .expect("Failed to embed"); + + assert_eq!(embedding.len(), 384); + } + + #[tokio::test] + async fn test_service_with_cache() { + let provider = FastEmbedProvider::small().expect("Failed to init"); + let cache = MemoryCache::new(100, Duration::from_secs(60)); + let service = EmbeddingService::new(provider).with_cache(cache); + + let options = EmbeddingOptions::default_with_cache(); + + let embedding1 = service + .embed("Hello world", &options) + .await + .expect("Failed first embed"); + assert_eq!(service.cache_size(), 1); + + let embedding2 = service + .embed("Hello world", &options) + .await + .expect("Failed second embed"); + assert_eq!(embedding1, embedding2); + } + + #[tokio::test] + async fn test_service_batch() { + let provider = FastEmbedProvider::small().expect("Failed to init"); + let cache = MemoryCache::with_defaults(); + let service = EmbeddingService::new(provider).with_cache(cache); + + let texts = vec![ + "Text 1".to_string(), + "Text 2".to_string(), + "Text 3".to_string(), + ]; + + let options = EmbeddingOptions::default_with_cache(); + let result = service + .embed_batch(texts.clone(), &options) + .await + .expect("Failed batch"); + + assert_eq!(result.embeddings.len(), 3); + assert_eq!(result.cached_count, 0); + + let result2 = service + .embed_batch(texts, &options) + .await + .expect("Failed second batch"); + assert_eq!(result2.cached_count, 3); + } + + #[tokio::test] + async fn test_service_cache_invalidation() { + let provider = FastEmbedProvider::small().expect("Failed to init"); + let cache = MemoryCache::with_defaults(); + let service = EmbeddingService::new(provider).with_cache(cache); + + let options = EmbeddingOptions::default_with_cache(); + + service + .embed("Test text", &options) + .await + .expect("Failed embed"); + assert_eq!(service.cache_size(), 1); + + service + .invalidate_cache("Test text") + .await + .expect("Failed to invalidate"); + assert_eq!(service.cache_size(), 0); + } + + #[tokio::test] + async fn test_service_clear_cache() { + let provider = FastEmbedProvider::small().expect("Failed to init"); + let cache = MemoryCache::with_defaults(); + let service = EmbeddingService::new(provider).with_cache(cache); + + let options = EmbeddingOptions::default_with_cache(); + + service.embed("Test 1", &options).await.expect("Failed"); + service.embed("Test 2", &options).await.expect("Failed"); + assert_eq!(service.cache_size(), 2); + + service.clear_cache().await.expect("Failed to clear"); + assert_eq!(service.cache_size(), 0); + } +} diff --git a/crates/stratum-embeddings/src/store/lancedb.rs b/crates/stratum-embeddings/src/store/lancedb.rs new file mode 100644 index 0000000..dbbeb56 --- /dev/null +++ b/crates/stratum-embeddings/src/store/lancedb.rs @@ -0,0 +1,430 @@ +#[cfg(feature = "lancedb-store")] +use std::sync::Arc; + +#[cfg(feature = "lancedb-store")] +use arrow::{ + array::{ + AsArray, FixedSizeListArray, Float32Array, RecordBatch, RecordBatchIterator, StringArray, + }, + datatypes::{DataType, Field, Float32Type, Schema}, +}; +#[cfg(feature = "lancedb-store")] +use async_trait::async_trait; +#[cfg(feature = "lancedb-store")] +use futures::TryStreamExt; +#[cfg(feature = "lancedb-store")] +use lancedb::{query::ExecutableQuery, query::QueryBase, Connection, DistanceType, Table}; + +#[cfg(feature = "lancedb-store")] +use crate::{ + error::EmbeddingError, + store::{SearchFilter, SearchResult, VectorStore, VectorStoreConfig}, + traits::Embedding, +}; + +pub struct LanceDbStore { + #[allow(dead_code)] + connection: Connection, + table: Table, + dimensions: usize, +} + +impl LanceDbStore { + pub async fn new( + path: &str, + table_name: &str, + config: VectorStoreConfig, + ) -> Result { + let connection = lancedb::connect(path).execute().await.map_err(|e| { + EmbeddingError::Initialization(format!("LanceDB connection failed: {}", e)) + })?; + + let table = match connection.open_table(table_name).execute().await { + Ok(t) => t, + Err(_) => { + let schema = Self::create_schema(config.dimensions); + let empty_batch = RecordBatch::new_empty(Arc::clone(&schema)); + let batches = RecordBatchIterator::new( + vec![Ok(empty_batch)].into_iter(), + Arc::clone(&schema), + ); + + connection + .create_table(table_name, batches) + .execute() + .await + .map_err(|e| { + EmbeddingError::Initialization(format!("Failed to create table: {}", e)) + })? + } + }; + + Ok(Self { + connection, + table, + dimensions: config.dimensions, + }) + } + + fn create_schema(dimensions: usize) -> Arc { + Arc::new(Schema::new(vec![ + Field::new("id", DataType::Utf8, false), + Field::new( + "vector", + DataType::FixedSizeList( + Arc::new(Field::new("item", DataType::Float32, true)), + dimensions as i32, + ), + false, + ), + Field::new("metadata", DataType::Utf8, true), + ])) + } + + fn create_record_batch( + ids: Vec, + embeddings: Vec, + metadata: Vec, + dimensions: usize, + ) -> Result { + let id_array = Arc::new(StringArray::from(ids)); + let metadata_array = Arc::new(StringArray::from(metadata)); + + let flat_values: Vec = embeddings.into_iter().flatten().collect(); + let values_array = Arc::new(Float32Array::from(flat_values)); + let vector_array = Arc::new(FixedSizeListArray::new( + Arc::new(Field::new("item", DataType::Float32, true)), + dimensions as i32, + values_array, + None, + )); + + let schema = Self::create_schema(dimensions); + + RecordBatch::try_new(schema, vec![id_array, vector_array, metadata_array]).map_err(|e| { + EmbeddingError::StoreError(format!("Failed to create record batch: {}", e)) + }) + } +} + +#[async_trait] +impl VectorStore for LanceDbStore { + fn name(&self) -> &str { + "lancedb" + } + + fn dimensions(&self) -> usize { + self.dimensions + } + + async fn upsert( + &self, + id: &str, + embedding: &Embedding, + metadata: serde_json::Value, + ) -> Result<(), EmbeddingError> { + if embedding.len() != self.dimensions { + return Err(EmbeddingError::DimensionMismatch { + expected: self.dimensions, + actual: embedding.len(), + }); + } + + let metadata_str = serde_json::to_string(&metadata) + .map_err(|e| EmbeddingError::SerializationError(e.to_string()))?; + + let batch = Self::create_record_batch( + vec![id.to_string()], + vec![embedding.clone()], + vec![metadata_str], + self.dimensions, + )?; + + let schema = Self::create_schema(self.dimensions); + let batches = RecordBatchIterator::new(vec![Ok(batch)].into_iter(), schema); + + self.table + .add(batches) + .execute() + .await + .map_err(|e| EmbeddingError::StoreError(format!("Failed to upsert: {}", e)))?; + + Ok(()) + } + + async fn upsert_batch( + &self, + items: Vec<(String, Embedding, serde_json::Value)>, + ) -> Result<(), EmbeddingError> { + if items.is_empty() { + return Ok(()); + } + + for (_, embedding, _) in &items { + if embedding.len() != self.dimensions { + return Err(EmbeddingError::DimensionMismatch { + expected: self.dimensions, + actual: embedding.len(), + }); + } + } + + let (ids, embeddings, metadata): (Vec<_>, Vec<_>, Vec<_>) = items.into_iter().fold( + (Vec::new(), Vec::new(), Vec::new()), + |(mut ids, mut embeddings, mut metadata), (id, embedding, meta)| { + ids.push(id); + embeddings.push(embedding); + metadata.push(serde_json::to_string(&meta).unwrap_or_default()); + (ids, embeddings, metadata) + }, + ); + + let batch = Self::create_record_batch(ids, embeddings, metadata, self.dimensions)?; + + let schema = Self::create_schema(self.dimensions); + let batches = RecordBatchIterator::new(vec![Ok(batch)].into_iter(), schema); + + self.table + .add(batches) + .execute() + .await + .map_err(|e| EmbeddingError::StoreError(format!("Failed to batch upsert: {}", e)))?; + + Ok(()) + } + + async fn search( + &self, + embedding: &Embedding, + limit: usize, + filter: Option, + ) -> Result, EmbeddingError> { + if embedding.len() != self.dimensions { + return Err(EmbeddingError::DimensionMismatch { + expected: self.dimensions, + actual: embedding.len(), + }); + } + + let mut query = self + .table + .vector_search(embedding.as_slice()) + .map_err(|e| EmbeddingError::StoreError(format!("Query setup failed: {}", e)))? + .distance_type(DistanceType::Cosine); + + if let Some(ref f) = filter { + if f.min_score.is_some() { + query = query.postfilter().refine_factor(10); + } + } + + let stream = + query.limit(limit).execute().await.map_err(|e| { + EmbeddingError::StoreError(format!("Query execution failed: {}", e)) + })?; + + let batches: Vec = stream + .try_collect() + .await + .map_err(|e| EmbeddingError::StoreError(format!("Failed to collect results: {}", e)))?; + + let mut search_results = Vec::new(); + + for batch in batches.iter() { + let ids: &Arc = + batch.column_by_name("id").ok_or_else(|| { + EmbeddingError::StoreError("Missing 'id' column in results".to_string()) + })?; + + let metadata_col: &Arc = + batch.column_by_name("metadata").ok_or_else(|| { + EmbeddingError::StoreError("Missing 'metadata' column in results".to_string()) + })?; + + let distance_col: Option<&Arc> = + batch.column_by_name("_distance"); + + let id_array = ids.as_string::(); + let metadata_array = metadata_col.as_string::(); + + let num_rows: usize = batch.num_rows(); + for i in 0..num_rows { + let id = id_array.value(i).to_string(); + + let metadata_str = metadata_array.value(i); + let metadata: serde_json::Value = + serde_json::from_str(metadata_str).unwrap_or(serde_json::json!({})); + + let score = if let Some(dist_col) = distance_col { + let dist_array = dist_col.as_primitive::(); + 1.0 - dist_array.value(i) + } else { + 0.0 + }; + + search_results.push(SearchResult { + id, + score, + embedding: None, + metadata, + }); + } + } + + if let Some(f) = filter { + if let Some(min_score) = f.min_score { + search_results.retain(|r| r.score >= min_score); + } + } + + Ok(search_results) + } + + async fn get(&self, id: &str) -> Result, EmbeddingError> { + let stream = self + .table + .query() + .only_if(format!("id = '{}'", id)) + .limit(1) + .execute() + .await + .map_err(|e| EmbeddingError::StoreError(format!("Query execution failed: {}", e)))?; + + let results: Vec = stream + .try_collect() + .await + .map_err(|e| EmbeddingError::StoreError(format!("Failed to collect results: {}", e)))?; + + for batch in results.iter() { + let num_rows: usize = batch.num_rows(); + if num_rows == 0 { + continue; + } + + let ids: &Arc = + batch.column_by_name("id").ok_or_else(|| { + EmbeddingError::StoreError("Missing 'id' column in results".to_string()) + })?; + + let metadata_col: &Arc = + batch.column_by_name("metadata").ok_or_else(|| { + EmbeddingError::StoreError("Missing 'metadata' column in results".to_string()) + })?; + + let id_array = ids.as_string::(); + let metadata_array = metadata_col.as_string::(); + + let result_id = id_array.value(0).to_string(); + let metadata_str = metadata_array.value(0); + let metadata: serde_json::Value = + serde_json::from_str(metadata_str).unwrap_or(serde_json::json!({})); + + return Ok(Some(SearchResult { + id: result_id, + score: 1.0, + embedding: None, + metadata, + })); + } + + Ok(None) + } + + async fn delete(&self, id: &str) -> Result { + self.table + .delete(&format!("id = '{}'", id)) + .await + .map_err(|e| EmbeddingError::StoreError(format!("Delete failed: {}", e)))?; + + Ok(true) + } + + async fn flush(&self) -> Result<(), EmbeddingError> { + Ok(()) + } + + async fn count(&self) -> Result { + let count = self + .table + .count_rows(None) + .await + .map_err(|e| EmbeddingError::StoreError(format!("Count failed: {}", e)))?; + + Ok(count) + } +} + +#[cfg(test)] +mod tests { + use tempfile::tempdir; + + use super::*; + + #[tokio::test] + async fn test_lancedb_store_basic() { + let dir = tempdir().expect("Failed to create temp dir"); + let path = dir.path().to_str().unwrap(); + + let config = VectorStoreConfig::new(384); + let store = LanceDbStore::new(path, "test_embeddings", config) + .await + .expect("Failed to create store"); + + let embedding = vec![0.1; 384]; + let metadata = serde_json::json!({"text": "hello world"}); + + store + .upsert("test_id", &embedding, metadata.clone()) + .await + .expect("Failed to upsert"); + + let result = store.get("test_id").await.expect("Failed to get"); + assert!(result.is_some()); + + let search_results = store + .search(&embedding, 5, None) + .await + .expect("Failed to search"); + assert!(!search_results.is_empty()); + + let count = store.count().await.expect("Failed to count"); + assert_eq!(count, 1); + } + + #[tokio::test] + async fn test_lancedb_store_batch() { + let dir = tempdir().expect("Failed to create temp dir"); + let path = dir.path().to_str().unwrap(); + + let config = VectorStoreConfig::new(384); + let store = LanceDbStore::new(path, "test_batch", config) + .await + .expect("Failed to create store"); + + let items = vec![ + ( + "id1".to_string(), + vec![0.1; 384], + serde_json::json!({"idx": 1}), + ), + ( + "id2".to_string(), + vec![0.2; 384], + serde_json::json!({"idx": 2}), + ), + ( + "id3".to_string(), + vec![0.3; 384], + serde_json::json!({"idx": 3}), + ), + ]; + + store + .upsert_batch(items) + .await + .expect("Failed to batch upsert"); + + let count = store.count().await.expect("Failed to count"); + assert_eq!(count, 3); + } +} diff --git a/crates/stratum-embeddings/src/store/mod.rs b/crates/stratum-embeddings/src/store/mod.rs new file mode 100644 index 0000000..72783db --- /dev/null +++ b/crates/stratum-embeddings/src/store/mod.rs @@ -0,0 +1,14 @@ +pub mod traits; + +#[cfg(feature = "lancedb-store")] +pub mod lancedb; + +#[cfg(feature = "surrealdb-store")] +pub mod surrealdb; + +pub use traits::*; + +#[cfg(feature = "lancedb-store")] +pub use self::lancedb::LanceDbStore; +#[cfg(feature = "surrealdb-store")] +pub use self::surrealdb::SurrealDbStore; diff --git a/crates/stratum-embeddings/src/store/surrealdb.rs b/crates/stratum-embeddings/src/store/surrealdb.rs new file mode 100644 index 0000000..9af3bda --- /dev/null +++ b/crates/stratum-embeddings/src/store/surrealdb.rs @@ -0,0 +1,298 @@ +#[cfg(feature = "surrealdb-store")] +use async_trait::async_trait; +#[cfg(feature = "surrealdb-store")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "surrealdb-store")] +use surrealdb::{ + engine::local::{Db, Mem}, + sql::Thing, + Surreal, +}; + +#[cfg(feature = "surrealdb-store")] +use crate::{ + error::EmbeddingError, + store::{SearchFilter, SearchResult, VectorStore, VectorStoreConfig}, + traits::Embedding, +}; + +#[derive(Debug, Serialize, Deserialize)] +struct EmbeddingRecord { + id: Option, + vector: Vec, + metadata: serde_json::Value, +} + +pub struct SurrealDbStore { + db: Surreal, + table: String, + dimensions: usize, +} + +impl SurrealDbStore { + pub async fn new( + connection: Surreal, + table_name: &str, + config: VectorStoreConfig, + ) -> Result { + Ok(Self { + db: connection, + table: table_name.to_string(), + dimensions: config.dimensions, + }) + } + + pub async fn new_memory( + table_name: &str, + config: VectorStoreConfig, + ) -> Result { + let db = Surreal::new::(()).await.map_err(|e| { + EmbeddingError::Initialization(format!("SurrealDB connection failed: {}", e)) + })?; + + db.use_ns("embeddings") + .use_db("embeddings") + .await + .map_err(|e| { + EmbeddingError::Initialization(format!("Failed to set namespace: {}", e)) + })?; + + Self::new(db, table_name, config).await + } + + fn compute_cosine_similarity(&self, a: &[f32], b: &[f32]) -> f32 { + if a.len() != b.len() { + return 0.0; + } + + let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum(); + let mag_a: f32 = a.iter().map(|x| x * x).sum::().sqrt(); + let mag_b: f32 = b.iter().map(|x| x * x).sum::().sqrt(); + + if mag_a > 0.0 && mag_b > 0.0 { + dot / (mag_a * mag_b) + } else { + 0.0 + } + } +} + +#[async_trait] +impl VectorStore for SurrealDbStore { + fn name(&self) -> &str { + "surrealdb" + } + + fn dimensions(&self) -> usize { + self.dimensions + } + + async fn upsert( + &self, + id: &str, + embedding: &Embedding, + metadata: serde_json::Value, + ) -> Result<(), EmbeddingError> { + if embedding.len() != self.dimensions { + return Err(EmbeddingError::DimensionMismatch { + expected: self.dimensions, + actual: embedding.len(), + }); + } + + let record = EmbeddingRecord { + id: None, + vector: embedding.clone(), + metadata, + }; + + let _: Option = self + .db + .update((self.table.as_str(), id)) + .content(record) + .await + .map_err(|e| EmbeddingError::StoreError(format!("Upsert failed: {}", e)))?; + + Ok(()) + } + + async fn search( + &self, + embedding: &Embedding, + limit: usize, + filter: Option, + ) -> Result, EmbeddingError> { + if embedding.len() != self.dimensions { + return Err(EmbeddingError::DimensionMismatch { + expected: self.dimensions, + actual: embedding.len(), + }); + } + + let all_records: Vec = self + .db + .select(&self.table) + .await + .map_err(|e| EmbeddingError::StoreError(format!("Search failed: {}", e)))?; + + let mut scored_results: Vec<(String, f32, serde_json::Value)> = all_records + .into_iter() + .filter_map(|record| { + let id = record.id?.id.to_string(); + let score = self.compute_cosine_similarity(embedding, &record.vector); + Some((id, score, record.metadata)) + }) + .collect(); + + scored_results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); + + if let Some(f) = filter { + if let Some(min_score) = f.min_score { + scored_results.retain(|(_, score, _)| *score >= min_score); + } + } + + let results = scored_results + .into_iter() + .take(limit) + .map(|(id, score, metadata)| SearchResult { + id, + score, + embedding: None, + metadata, + }) + .collect(); + + Ok(results) + } + + async fn get(&self, id: &str) -> Result, EmbeddingError> { + let record: Option = self + .db + .select((self.table.as_str(), id)) + .await + .map_err(|e| EmbeddingError::StoreError(format!("Get failed: {}", e)))?; + + Ok(record.map(|r| SearchResult { + id: id.to_string(), + score: 1.0, + embedding: Some(r.vector), + metadata: r.metadata, + })) + } + + async fn delete(&self, id: &str) -> Result { + let result: Option = self + .db + .delete((self.table.as_str(), id)) + .await + .map_err(|e| EmbeddingError::StoreError(format!("Delete failed: {}", e)))?; + + Ok(result.is_some()) + } + + async fn flush(&self) -> Result<(), EmbeddingError> { + Ok(()) + } + + async fn count(&self) -> Result { + let records: Vec = self + .db + .select(&self.table) + .await + .map_err(|e| EmbeddingError::StoreError(format!("Count failed: {}", e)))?; + + Ok(records.len()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_surrealdb_store_basic() { + let config = VectorStoreConfig::new(384); + let store = SurrealDbStore::new_memory("test_embeddings", config) + .await + .expect("Failed to create store"); + + let embedding = vec![0.1; 384]; + let metadata = serde_json::json!({"text": "hello world"}); + + store + .upsert("test_id", &embedding, metadata.clone()) + .await + .expect("Failed to upsert"); + + let result = store.get("test_id").await.expect("Failed to get"); + assert!(result.is_some()); + assert_eq!(result.as_ref().unwrap().id, "test_id"); + assert_eq!(result.as_ref().unwrap().metadata, metadata); + + let search_results = store + .search(&embedding, 5, None) + .await + .expect("Failed to search"); + assert_eq!(search_results.len(), 1); + assert!(search_results[0].score > 0.99); + + let count = store.count().await.expect("Failed to count"); + assert_eq!(count, 1); + } + + #[tokio::test] + async fn test_surrealdb_store_search() { + let config = VectorStoreConfig::new(3); + let store = SurrealDbStore::new_memory("test_search", config) + .await + .expect("Failed to create store"); + + let embeddings = [ + vec![1.0, 0.0, 0.0], + vec![0.0, 1.0, 0.0], + vec![0.5, 0.5, 0.0], + ]; + + for (i, embedding) in embeddings.iter().enumerate() { + store + .upsert( + &format!("id_{}", i), + embedding, + serde_json::json!({"idx": i}), + ) + .await + .expect("Failed to upsert"); + } + + let query = vec![1.0, 0.0, 0.0]; + let results = store + .search(&query, 2, None) + .await + .expect("Failed to search"); + + assert_eq!(results.len(), 2); + assert_eq!(results[0].id, "id_0"); + assert!(results[0].score > 0.99); + } + + #[tokio::test] + async fn test_surrealdb_store_delete() { + let config = VectorStoreConfig::new(384); + let store = SurrealDbStore::new_memory("test_delete", config) + .await + .expect("Failed to create store"); + + let embedding = vec![0.1; 384]; + store + .upsert("test_id", &embedding, serde_json::json!({})) + .await + .expect("Failed to upsert"); + + let deleted = store.delete("test_id").await.expect("Failed to delete"); + assert!(deleted); + + let result = store.get("test_id").await.expect("Failed to get"); + assert!(result.is_none()); + } +} diff --git a/crates/stratum-embeddings/src/store/traits.rs b/crates/stratum-embeddings/src/store/traits.rs new file mode 100644 index 0000000..5f932c3 --- /dev/null +++ b/crates/stratum-embeddings/src/store/traits.rs @@ -0,0 +1,102 @@ +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +use crate::{error::EmbeddingError, traits::Embedding}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchResult { + pub id: String, + pub score: f32, + #[serde(skip_serializing_if = "Option::is_none")] + pub embedding: Option, + pub metadata: serde_json::Value, +} + +#[derive(Debug, Clone, Default)] +pub struct SearchFilter { + pub metadata: Option, + pub min_score: Option, +} + +#[derive(Debug, Clone)] +pub struct VectorStoreConfig { + pub dimensions: usize, + pub metric: DistanceMetric, + pub options: serde_json::Value, +} + +impl VectorStoreConfig { + pub fn new(dimensions: usize) -> Self { + Self { + dimensions, + metric: DistanceMetric::default(), + options: serde_json::json!({}), + } + } + + pub fn with_metric(mut self, metric: DistanceMetric) -> Self { + self.metric = metric; + self + } + + pub fn with_options(mut self, options: serde_json::Value) -> Self { + self.options = options; + self + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum DistanceMetric { + #[default] + Cosine, + Euclidean, + DotProduct, +} + +#[async_trait] +pub trait VectorStore: Send + Sync { + fn name(&self) -> &str; + fn dimensions(&self) -> usize; + + async fn upsert( + &self, + id: &str, + embedding: &Embedding, + metadata: serde_json::Value, + ) -> Result<(), EmbeddingError>; + + async fn upsert_batch( + &self, + items: Vec<(String, Embedding, serde_json::Value)>, + ) -> Result<(), EmbeddingError> { + for (id, embedding, metadata) in items { + self.upsert(&id, &embedding, metadata).await?; + } + Ok(()) + } + + async fn search( + &self, + embedding: &Embedding, + limit: usize, + filter: Option, + ) -> Result, EmbeddingError>; + + async fn get(&self, id: &str) -> Result, EmbeddingError>; + + async fn delete(&self, id: &str) -> Result; + + async fn delete_batch(&self, ids: &[&str]) -> Result { + let mut count = 0; + for id in ids { + if self.delete(id).await? { + count += 1; + } + } + Ok(count) + } + + async fn flush(&self) -> Result<(), EmbeddingError>; + + async fn count(&self) -> Result; +} diff --git a/crates/stratum-embeddings/src/traits.rs b/crates/stratum-embeddings/src/traits.rs new file mode 100644 index 0000000..916ff52 --- /dev/null +++ b/crates/stratum-embeddings/src/traits.rs @@ -0,0 +1,162 @@ +use async_trait::async_trait; + +pub type Embedding = Vec; + +#[derive(Debug, Clone)] +pub struct EmbeddingResult { + pub embeddings: Vec, + pub model: String, + pub dimensions: usize, + pub total_tokens: Option, + pub cached_count: usize, +} + +#[derive(Debug, Clone, Default)] +pub struct EmbeddingOptions { + pub normalize: bool, + pub truncate: bool, + pub use_cache: bool, +} + +impl EmbeddingOptions { + pub fn default_with_cache() -> Self { + Self { + normalize: true, + truncate: true, + use_cache: true, + } + } + + pub fn no_cache() -> Self { + Self { + normalize: true, + truncate: true, + use_cache: false, + } + } +} + +#[async_trait] +pub trait EmbeddingProvider: Send + Sync { + fn name(&self) -> &str; + fn model(&self) -> &str; + fn dimensions(&self) -> usize; + fn is_local(&self) -> bool; + fn max_tokens(&self) -> usize; + fn max_batch_size(&self) -> usize; + fn cost_per_1m_tokens(&self) -> f64; + + async fn is_available(&self) -> bool; + + async fn embed( + &self, + text: &str, + options: &EmbeddingOptions, + ) -> Result; + + async fn embed_batch( + &self, + texts: &[&str], + options: &EmbeddingOptions, + ) -> Result; +} + +#[derive(Debug, Clone)] +pub struct ProviderInfo { + pub name: String, + pub model: String, + pub dimensions: usize, + pub is_local: bool, + pub cost_per_1m: f64, + pub max_batch_size: usize, +} + +impl From<&T> for ProviderInfo { + fn from(provider: &T) -> Self { + Self { + name: provider.name().to_string(), + model: provider.model().to_string(), + dimensions: provider.dimensions(), + is_local: provider.is_local(), + cost_per_1m: provider.cost_per_1m_tokens(), + max_batch_size: provider.max_batch_size(), + } + } +} + +pub fn normalize_embedding(embedding: &mut Embedding) { + let magnitude: f32 = embedding.iter().map(|x| x * x).sum::().sqrt(); + if magnitude > 0.0 { + embedding.iter_mut().for_each(|x| *x /= magnitude); + } +} + +pub fn cosine_similarity(a: &Embedding, b: &Embedding) -> f32 { + if a.len() != b.len() { + return 0.0; + } + + let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum(); + let magnitude_a: f32 = a.iter().map(|x| x * x).sum::().sqrt(); + let magnitude_b: f32 = b.iter().map(|x| x * x).sum::().sqrt(); + + if magnitude_a > 0.0 && magnitude_b > 0.0 { + dot_product / (magnitude_a * magnitude_b) + } else { + 0.0 + } +} + +pub fn euclidean_distance(a: &Embedding, b: &Embedding) -> f32 { + if a.len() != b.len() { + return f32::MAX; + } + + a.iter() + .zip(b.iter()) + .map(|(x, y)| (x - y).powi(2)) + .sum::() + .sqrt() +} + +#[cfg(test)] +mod tests { + use approx::assert_relative_eq; + + use super::*; + + #[test] + fn test_normalize_embedding() { + let mut embedding = vec![3.0, 4.0]; + normalize_embedding(&mut embedding); + assert_relative_eq!(embedding[0], 0.6, epsilon = 0.0001); + assert_relative_eq!(embedding[1], 0.8, epsilon = 0.0001); + + let magnitude: f32 = embedding.iter().map(|x| x * x).sum::().sqrt(); + assert_relative_eq!(magnitude, 1.0, epsilon = 0.0001); + } + + #[test] + fn test_cosine_similarity() { + let a = vec![1.0, 0.0, 0.0]; + let b = vec![1.0, 0.0, 0.0]; + assert_relative_eq!(cosine_similarity(&a, &b), 1.0, epsilon = 0.0001); + + let c = vec![0.0, 1.0, 0.0]; + assert_relative_eq!(cosine_similarity(&a, &c), 0.0, epsilon = 0.0001); + + let d = vec![-1.0, 0.0, 0.0]; + assert_relative_eq!(cosine_similarity(&a, &d), -1.0, epsilon = 0.0001); + } + + #[test] + fn test_euclidean_distance() { + let a = vec![0.0, 0.0]; + let b = vec![3.0, 4.0]; + assert_relative_eq!(euclidean_distance(&a, &b), 5.0, epsilon = 0.0001); + + let c = vec![1.0, 1.0]; + let d = vec![1.0, 1.0]; + assert_relative_eq!(euclidean_distance(&c, &d), 0.0, epsilon = 0.0001); + } +} diff --git a/crates/stratum-llm/Cargo.toml b/crates/stratum-llm/Cargo.toml new file mode 100644 index 0000000..bf54f01 --- /dev/null +++ b/crates/stratum-llm/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "stratum-llm" +version = "0.1.0" +edition.workspace = true +description = "Unified LLM abstraction with CLI detection, fallback, and caching" +license.workspace = true + +[dependencies] +# Async runtime +tokio = { workspace = true } +async-trait = { workspace = true } +futures = { workspace = true } + +# HTTP client +reqwest = { workspace = true, features = ["stream"] } + +# Serialization +serde = { workspace = true } +serde_json = { workspace = true } +serde_yaml = { workspace = true, optional = true } + +# Caching +moka = { workspace = true } + +# Error handling +thiserror = { workspace = true } + +# Logging +tracing = { workspace = true } + +# Metrics +prometheus = { workspace = true, optional = true } + +# Utilities +dirs = { workspace = true } +chrono = { workspace = true } +uuid = { workspace = true, features = ["v4"] } +which = { workspace = true, optional = true } + +# Hashing for cache keys +xxhash-rust = { workspace = true } + +[features] +default = ["anthropic", "openai", "ollama"] +anthropic = [] +openai = [] +deepseek = [] +ollama = [] +claude-cli = [] +openai-cli = [] +kogral = ["serde_yaml", "which"] +metrics = ["prometheus"] +all = [ + "anthropic", + "openai", + "deepseek", + "ollama", + "claude-cli", + "openai-cli", + "kogral", + "metrics", +] + +[dev-dependencies] +tokio-test = { workspace = true } diff --git a/crates/stratum-llm/README.md b/crates/stratum-llm/README.md new file mode 100644 index 0000000..dcfc46b --- /dev/null +++ b/crates/stratum-llm/README.md @@ -0,0 +1,131 @@ +# stratum-llm + +Unified LLM abstraction for the stratumiops ecosystem with automatic provider detection, fallback chains, and smart caching. + +## Features + +- **Credential Auto-detection**: Automatically finds CLI credentials (Claude, OpenAI) and API keys +- **Provider Fallback**: Circuit breaker pattern with automatic failover across providers +- **Smart Caching**: xxHash-based request deduplication reduces duplicate API calls +- **Kogral Integration**: Inject project context from knowledge base (optional) +- **Cost Tracking**: Transparent cost estimation across all providers +- **Multiple Providers**: Anthropic Claude, OpenAI, DeepSeek, Ollama + +## Quick Start + +```rust +use stratum_llm::{UnifiedClient, Message, Role}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = UnifiedClient::auto()?; + + let messages = vec![ + Message { + role: Role::User, + content: "What is Rust?".to_string(), + } + ]; + + let response = client.generate(&messages, None).await?; + println!("{}", response.content); + + Ok(()) +} +``` + +## Provider Priority + +1. **CLI credentials** (subscription-based, no per-token cost) - preferred +2. **API keys** from environment variables +3. **Local models** (Ollama) + +The client automatically detects available credentials and builds a fallback chain. + +## Features + +### Default Features + +```toml +[dependencies] +stratum-llm = "0.1" +``` + +Includes: Anthropic, OpenAI, Ollama + +### All Features + +```toml +[dependencies] +stratum-llm = { version = "0.1", features = ["all"] } +``` + +Includes: All providers, CLI detection, Kogral integration, Prometheus metrics + +### Custom Feature Set + +```toml +[dependencies] +stratum-llm = { version = "0.1", features = ["anthropic", "deepseek", "kogral"] } +``` + +Available features: + +- `anthropic` - Anthropic Claude API +- `openai` - OpenAI API +- `deepseek` - DeepSeek API +- `ollama` - Ollama local models +- `claude-cli` - Claude CLI credential detection +- `kogral` - Kogral knowledge base integration +- `metrics` - Prometheus metrics + +## Advanced Usage + +### With Kogral Context + +```rust +let client = UnifiedClient::builder() + .auto_detect()? + .with_kogral() + .build()?; + +let response = client + .generate_with_kogral(&messages, None, Some("rust"), None) + .await?; +``` + +### Custom Fallback Strategy + +```rust +use stratum_llm::{FallbackStrategy, ProviderChain}; + +let chain = ProviderChain::from_detected()? + .with_strategy(FallbackStrategy::OnRateLimitOrUnavailable); + +let client = UnifiedClient::builder() + .with_chain(chain) + .build()?; +``` + +### Cost Budget + +```rust +let chain = ProviderChain::from_detected()? + .with_strategy(FallbackStrategy::OnBudgetExceeded { + budget_cents: 10.0, + }); +``` + +## Examples + +Run examples with: + +```bash +cargo run --example basic_usage +cargo run --example with_kogral --features kogral +cargo run --example fallback_demo +``` + +## License + +MIT OR Apache-2.0 diff --git a/crates/stratum-llm/examples/basic_usage.rs b/crates/stratum-llm/examples/basic_usage.rs new file mode 100644 index 0000000..61eb999 --- /dev/null +++ b/crates/stratum-llm/examples/basic_usage.rs @@ -0,0 +1,43 @@ +use stratum_llm::{Message, Role, UnifiedClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + println!("Creating UnifiedClient with auto-detected providers..."); + let client = UnifiedClient::auto()?; + + println!("\nAvailable providers:"); + for provider in client.providers() { + println!( + " - {} ({}): circuit={:?}, subscription={}", + provider.name, provider.model, provider.circuit_state, provider.is_subscription + ); + } + + let messages = vec![Message { + role: Role::User, + content: "What is the capital of France? Answer in one word.".to_string(), + }]; + + println!("\nSending request..."); + match client.generate(&messages, None).await { + Ok(response) => { + println!("\n✓ Success!"); + println!("Provider: {}", response.provider); + println!("Model: {}", response.model); + println!("Response: {}", response.content); + println!( + "Tokens: {} in, {} out", + response.input_tokens, response.output_tokens + ); + println!("Cost: ${:.4}", response.cost_cents / 100.0); + println!("Latency: {}ms", response.latency_ms); + } + Err(e) => { + eprintln!("\n✗ Error: {}", e); + } + } + + Ok(()) +} diff --git a/crates/stratum-llm/examples/fallback_demo.rs b/crates/stratum-llm/examples/fallback_demo.rs new file mode 100644 index 0000000..f07420b --- /dev/null +++ b/crates/stratum-llm/examples/fallback_demo.rs @@ -0,0 +1,54 @@ +use stratum_llm::{FallbackStrategy, Message, Role, UnifiedClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + println!("Creating UnifiedClient with budget-based fallback strategy..."); + + let client = UnifiedClient::builder().auto_detect()?.build()?; + + let messages = vec![Message { + role: Role::User, + content: "Explain quantum computing in simple terms.".to_string(), + }]; + + println!("\nProvider chain:"); + for (idx, provider) in client.providers().iter().enumerate() { + println!( + " {}. {} ({}) - circuit: {:?}", + idx + 1, + provider.name, + provider.model, + provider.circuit_state + ); + } + + println!("\nSending multiple requests to test fallback..."); + + for i in 1..=3 { + println!("\n--- Request {} ---", i); + match client.generate(&messages, None).await { + Ok(response) => { + println!( + "✓ Provider: {} | Model: {} | Cost: ${:.4} | Latency: {}ms", + response.provider, + response.model, + response.cost_cents / 100.0, + response.latency_ms + ); + println!( + "Response preview: {}...", + &response.content[..100.min(response.content.len())] + ); + } + Err(e) => { + eprintln!("✗ All providers failed: {}", e); + } + } + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + + Ok(()) +} diff --git a/crates/stratum-llm/examples/with_kogral.rs b/crates/stratum-llm/examples/with_kogral.rs new file mode 100644 index 0000000..c7bba4c --- /dev/null +++ b/crates/stratum-llm/examples/with_kogral.rs @@ -0,0 +1,45 @@ +#[cfg(feature = "kogral")] +use stratum_llm::{Message, Role, UnifiedClient}; + +#[cfg(feature = "kogral")] +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + + println!("Creating UnifiedClient with Kogral integration..."); + let client = UnifiedClient::builder() + .auto_detect()? + .with_kogral() + .build()?; + + let messages = vec![Message { + role: Role::User, + content: "Write a simple Rust function to add two numbers.".to_string(), + }]; + + println!("\nSending request with Rust guidelines from Kogral..."); + match client + .generate_with_kogral(&messages, None, Some("rust"), None) + .await + { + Ok(response) => { + println!("\n✓ Success!"); + println!("Provider: {}", response.provider); + println!("Model: {}", response.model); + println!("Response:\n{}", response.content); + println!("\nCost: ${:.4}", response.cost_cents / 100.0); + println!("Latency: {}ms", response.latency_ms); + } + Err(e) => { + eprintln!("\n✗ Error: {}", e); + } + } + + Ok(()) +} + +#[cfg(not(feature = "kogral"))] +fn main() { + eprintln!("This example requires the 'kogral' feature."); + eprintln!("Run with: cargo run --example with_kogral --features kogral"); +} diff --git a/crates/stratum-llm/src/cache/mod.rs b/crates/stratum-llm/src/cache/mod.rs new file mode 100644 index 0000000..202e899 --- /dev/null +++ b/crates/stratum-llm/src/cache/mod.rs @@ -0,0 +1,3 @@ +pub mod request_cache; + +pub use request_cache::{CacheConfig, CacheStats, CachedResponse, RequestCache}; diff --git a/crates/stratum-llm/src/cache/request_cache.rs b/crates/stratum-llm/src/cache/request_cache.rs new file mode 100644 index 0000000..1352320 --- /dev/null +++ b/crates/stratum-llm/src/cache/request_cache.rs @@ -0,0 +1,151 @@ +use std::time::Duration; + +use moka::future::Cache; +use xxhash_rust::xxh3::xxh3_64; + +#[derive(Clone)] +pub struct CachedResponse { + pub content: String, + pub model: String, + pub provider: String, + pub cached_at: chrono::DateTime, +} + +pub struct RequestCache { + cache: Cache, + enabled: bool, +} + +#[derive(Debug, Clone)] +pub struct CacheConfig { + pub enabled: bool, + pub max_entries: u64, + pub ttl: Duration, +} + +impl Default for CacheConfig { + fn default() -> Self { + Self { + enabled: true, + max_entries: 1000, + ttl: Duration::from_secs(3600), + } + } +} + +impl RequestCache { + pub fn new(config: CacheConfig) -> Self { + let cache = Cache::builder() + .max_capacity(config.max_entries) + .time_to_live(config.ttl) + .build(); + + Self { + cache, + enabled: config.enabled, + } + } + + fn compute_key( + &self, + messages: &[crate::providers::Message], + options: &crate::providers::GenerationOptions, + ) -> u64 { + let mut hasher_input = String::new(); + + for msg in messages { + hasher_input.push_str(&format!("{:?}:{}\\n", msg.role, msg.content)); + } + + hasher_input.push_str(&format!( + "temp:{:?}|max:{:?}|top_p:{:?}", + options.temperature, options.max_tokens, options.top_p, + )); + + xxh3_64(hasher_input.as_bytes()) + } + + pub async fn get( + &self, + messages: &[crate::providers::Message], + options: &crate::providers::GenerationOptions, + ) -> Option { + if !self.enabled { + return None; + } + + let key = self.compute_key(messages, options); + self.cache.get(&key).await + } + + pub async fn put( + &self, + messages: &[crate::providers::Message], + options: &crate::providers::GenerationOptions, + response: &crate::providers::GenerationResponse, + ) { + if !self.enabled { + return; + } + + let key = self.compute_key(messages, options); + let cached = CachedResponse { + content: response.content.clone(), + model: response.model.clone(), + provider: response.provider.clone(), + cached_at: chrono::Utc::now(), + }; + + self.cache.insert(key, cached).await; + } + + pub async fn get_or_generate( + &self, + messages: &[crate::providers::Message], + options: &crate::providers::GenerationOptions, + generate: F, + ) -> Result + where + F: FnOnce() -> Fut, + Fut: std::future::Future< + Output = Result, + >, + { + if let Some(cached) = self.get(messages, options).await { + tracing::debug!("Cache hit"); + return Ok(crate::providers::GenerationResponse { + content: cached.content, + model: cached.model, + provider: cached.provider, + input_tokens: 0, + output_tokens: 0, + cost_cents: 0.0, + latency_ms: 0, + }); + } + + let response = generate().await?; + self.put(messages, options, &response).await; + + Ok(response) + } + + pub fn stats(&self) -> CacheStats { + CacheStats { + entry_count: self.cache.entry_count(), + hit_count: 0, + miss_count: 0, + } + } + + pub fn clear(&self) { + self.cache.invalidate_all(); + } +} + +#[derive(Debug)] +pub struct CacheStats { + pub entry_count: u64, + pub hit_count: u64, + pub miss_count: u64, +} diff --git a/crates/stratum-llm/src/chain/circuit_breaker.rs b/crates/stratum-llm/src/chain/circuit_breaker.rs new file mode 100644 index 0000000..537fac6 --- /dev/null +++ b/crates/stratum-llm/src/chain/circuit_breaker.rs @@ -0,0 +1,146 @@ +use std::sync::atomic::{AtomicU32, AtomicU8, Ordering}; +use std::sync::RwLock; +use std::time::{Duration, Instant}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum CircuitState { + Closed = 0, + Open = 1, + HalfOpen = 2, +} + +pub struct CircuitBreaker { + state: AtomicU8, + failure_count: AtomicU32, + success_count: AtomicU32, + config: CircuitBreakerConfig, + last_failure_time: RwLock>, + last_success_time: RwLock>, +} + +#[derive(Debug, Clone)] +pub struct CircuitBreakerConfig { + pub failure_threshold: u32, + pub success_threshold: u32, + pub reset_timeout: Duration, + pub request_timeout: Duration, +} + +impl Default for CircuitBreakerConfig { + fn default() -> Self { + Self { + failure_threshold: 5, + success_threshold: 3, + reset_timeout: Duration::from_secs(30), + request_timeout: Duration::from_secs(60), + } + } +} + +impl CircuitBreaker { + pub fn new(config: CircuitBreakerConfig) -> Self { + Self { + state: AtomicU8::new(CircuitState::Closed as u8), + failure_count: AtomicU32::new(0), + success_count: AtomicU32::new(0), + config, + last_failure_time: RwLock::new(None), + last_success_time: RwLock::new(None), + } + } + + pub fn state(&self) -> CircuitState { + match self.state.load(Ordering::SeqCst) { + 0 => CircuitState::Closed, + 1 => CircuitState::Open, + 2 => CircuitState::HalfOpen, + _ => CircuitState::Closed, + } + } + + pub fn should_allow(&self) -> bool { + match self.state() { + CircuitState::Closed => true, + CircuitState::Open => { + if let Some(last_failure) = *self.last_failure_time.read().unwrap() { + if last_failure.elapsed() >= self.config.reset_timeout { + self.state + .store(CircuitState::HalfOpen as u8, Ordering::SeqCst); + self.success_count.store(0, Ordering::SeqCst); + return true; + } + } + false + } + CircuitState::HalfOpen => true, + } + } + + pub fn record_success(&self) { + *self.last_success_time.write().unwrap() = Some(Instant::now()); + self.failure_count.store(0, Ordering::SeqCst); + + if self.state() == CircuitState::HalfOpen { + let count = self.success_count.fetch_add(1, Ordering::SeqCst) + 1; + if count >= self.config.success_threshold { + self.state + .store(CircuitState::Closed as u8, Ordering::SeqCst); + tracing::info!("Circuit breaker closed (recovered)"); + } + } + } + + pub fn record_failure(&self) { + *self.last_failure_time.write().unwrap() = Some(Instant::now()); + + match self.state() { + CircuitState::Closed => { + let count = self.failure_count.fetch_add(1, Ordering::SeqCst) + 1; + if count >= self.config.failure_threshold { + self.state.store(CircuitState::Open as u8, Ordering::SeqCst); + tracing::warn!( + failures = count, + "Circuit breaker opened (too many failures)" + ); + } + } + CircuitState::HalfOpen => { + self.state.store(CircuitState::Open as u8, Ordering::SeqCst); + self.success_count.store(0, Ordering::SeqCst); + tracing::warn!("Circuit breaker reopened (half-open test failed)"); + } + CircuitState::Open => {} + } + } + + pub async fn call(&self, f: F) -> Result> + where + F: std::future::Future>, + { + if !self.should_allow() { + return Err(CircuitError::Open); + } + + match tokio::time::timeout(self.config.request_timeout, f).await { + Ok(Ok(result)) => { + self.record_success(); + Ok(result) + } + Ok(Err(e)) => { + self.record_failure(); + Err(CircuitError::Inner(e)) + } + Err(_) => { + self.record_failure(); + Err(CircuitError::Timeout) + } + } + } +} + +#[derive(Debug)] +pub enum CircuitError { + Open, + Timeout, + Inner(E), +} diff --git a/crates/stratum-llm/src/chain/mod.rs b/crates/stratum-llm/src/chain/mod.rs new file mode 100644 index 0000000..9ce9731 --- /dev/null +++ b/crates/stratum-llm/src/chain/mod.rs @@ -0,0 +1,5 @@ +pub mod circuit_breaker; +pub mod provider_chain; + +pub use circuit_breaker::{CircuitBreaker, CircuitBreakerConfig, CircuitError, CircuitState}; +pub use provider_chain::{FallbackStrategy, ProviderChain, ProviderInfo}; diff --git a/crates/stratum-llm/src/chain/provider_chain.rs b/crates/stratum-llm/src/chain/provider_chain.rs new file mode 100644 index 0000000..779e07d --- /dev/null +++ b/crates/stratum-llm/src/chain/provider_chain.rs @@ -0,0 +1,208 @@ +use crate::chain::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig, CircuitError}; +use crate::credentials::CredentialDetector; +use crate::error::LlmError; +use crate::providers::{ + ConfiguredProvider, GenerationOptions, GenerationResponse, LlmProvider, Message, +}; + +pub struct ProviderChain { + providers: Vec, + strategy: FallbackStrategy, +} + +struct ProviderWithCircuit { + provider: Box, + circuit: CircuitBreaker, + is_subscription: bool, + priority: u32, +} + +#[derive(Clone)] +pub enum FallbackStrategy { + Sequential, + OnRateLimitOrUnavailable, + OnBudgetExceeded { budget_cents: f64 }, +} + +impl ProviderChain { + pub fn from_detected() -> Result { + let detector = CredentialDetector::new(); + let credentials = detector.detect_all(); + + if credentials.is_empty() { + return Err(LlmError::NoProvidersAvailable); + } + + let mut providers = Vec::new(); + + for cred in credentials { + let provider: Option> = match cred.provider.as_str() { + #[cfg(feature = "anthropic")] + "anthropic" => Some(Box::new( + crate::providers::AnthropicProvider::sonnet() + .map_err(|_| LlmError::NoProvidersAvailable)?, + )), + #[cfg(feature = "openai")] + "openai" => Some(Box::new( + crate::providers::OpenAiProvider::gpt4o() + .map_err(|_| LlmError::NoProvidersAvailable)?, + )), + #[cfg(feature = "deepseek")] + "deepseek" => Some(Box::new( + crate::providers::DeepSeekProvider::coder() + .map_err(|_| LlmError::NoProvidersAvailable)?, + )), + #[cfg(feature = "ollama")] + "ollama" => Some(Box::new(crate::providers::OllamaProvider::default())), + _ => None, + }; + + if let Some(provider) = provider { + providers.push(ProviderWithCircuit { + provider, + circuit: CircuitBreaker::new(CircuitBreakerConfig::default()), + is_subscription: cred.is_subscription, + priority: if cred.is_subscription { 0 } else { 10 }, + }); + } + } + + if providers.is_empty() { + return Err(LlmError::NoProvidersAvailable); + } + + providers.sort_by_key(|p| p.priority); + + Ok(Self { + providers, + strategy: FallbackStrategy::Sequential, + }) + } + + pub fn with_providers(providers: Vec) -> Self { + let providers = providers + .into_iter() + .map(|p| ProviderWithCircuit { + provider: p.provider, + circuit: CircuitBreaker::new(CircuitBreakerConfig::default()), + is_subscription: matches!( + p.credential_source, + crate::providers::CredentialSource::Cli { .. } + ), + priority: p.priority, + }) + .collect(); + + Self { + providers, + strategy: FallbackStrategy::Sequential, + } + } + + pub fn with_strategy(mut self, strategy: FallbackStrategy) -> Self { + self.strategy = strategy; + self + } + + pub async fn generate( + &self, + messages: &[Message], + options: &GenerationOptions, + ) -> Result { + let mut last_error: Option = None; + + for pwc in &self.providers { + if !pwc.circuit.should_allow() { + tracing::debug!(provider = pwc.provider.name(), "Circuit open, skipping"); + continue; + } + + if let FallbackStrategy::OnBudgetExceeded { budget_cents } = &self.strategy { + let estimated_cost = pwc.provider.estimate_cost( + estimate_tokens(messages), + options.max_tokens.unwrap_or(1000), + ); + if estimated_cost > *budget_cents { + tracing::debug!( + provider = pwc.provider.name(), + estimated_cost, + budget = budget_cents, + "Would exceed budget, trying next" + ); + continue; + } + } + + match pwc + .circuit + .call(pwc.provider.generate(messages, options)) + .await + { + Ok(response) => { + tracing::info!( + provider = pwc.provider.name(), + model = pwc.provider.model(), + cost_cents = response.cost_cents, + latency_ms = response.latency_ms, + "Request successful" + ); + return Ok(response); + } + Err(CircuitError::Open) => { + tracing::debug!(provider = pwc.provider.name(), "Circuit open"); + continue; + } + Err(CircuitError::Timeout) => { + tracing::warn!(provider = pwc.provider.name(), "Request timed out"); + last_error = Some(LlmError::Timeout); + continue; + } + Err(CircuitError::Inner(e)) => { + tracing::warn!(provider = pwc.provider.name(), error = %e, "Request failed"); + + let should_fallback = match &self.strategy { + FallbackStrategy::Sequential => true, + FallbackStrategy::OnRateLimitOrUnavailable => { + matches!(e, LlmError::RateLimit(_) | LlmError::Unavailable(_)) + } + FallbackStrategy::OnBudgetExceeded { .. } => true, + }; + + if should_fallback { + last_error = Some(e); + continue; + } else { + return Err(e); + } + } + } + } + + Err(last_error.unwrap_or(LlmError::NoProvidersAvailable)) + } + + pub fn provider_info(&self) -> Vec { + self.providers + .iter() + .map(|p| ProviderInfo { + name: p.provider.name().to_string(), + model: p.provider.model().to_string(), + is_subscription: p.is_subscription, + circuit_state: p.circuit.state(), + }) + .collect() + } +} + +#[derive(Debug)] +pub struct ProviderInfo { + pub name: String, + pub model: String, + pub is_subscription: bool, + pub circuit_state: crate::chain::circuit_breaker::CircuitState, +} + +fn estimate_tokens(messages: &[Message]) -> u32 { + let total_chars: usize = messages.iter().map(|m| m.content.len()).sum(); + (total_chars / 4) as u32 +} diff --git a/crates/stratum-llm/src/client.rs b/crates/stratum-llm/src/client.rs new file mode 100644 index 0000000..09a752a --- /dev/null +++ b/crates/stratum-llm/src/client.rs @@ -0,0 +1,167 @@ +use crate::cache::{CacheConfig, RequestCache}; +use crate::chain::ProviderChain; +use crate::error::LlmError; +#[cfg(feature = "kogral")] +use crate::kogral::KogralIntegration; +use crate::providers::{GenerationOptions, GenerationResponse, Message}; + +pub struct UnifiedClient { + chain: ProviderChain, + cache: RequestCache, + #[cfg(feature = "kogral")] + kogral: Option, + default_options: GenerationOptions, +} + +pub struct UnifiedClientBuilder { + chain: Option, + cache_config: CacheConfig, + #[cfg(feature = "kogral")] + kogral: Option, + default_options: GenerationOptions, +} + +impl UnifiedClientBuilder { + pub fn new() -> Self { + Self { + chain: None, + cache_config: CacheConfig::default(), + #[cfg(feature = "kogral")] + kogral: None, + default_options: GenerationOptions::default(), + } + } + + pub fn auto_detect(mut self) -> Result { + self.chain = Some(ProviderChain::from_detected()?); + Ok(self) + } + + pub fn with_chain(mut self, chain: ProviderChain) -> Self { + self.chain = Some(chain); + self + } + + pub fn with_cache(mut self, config: CacheConfig) -> Self { + self.cache_config = config; + self + } + + pub fn without_cache(mut self) -> Self { + self.cache_config.enabled = false; + self + } + + #[cfg(feature = "kogral")] + pub fn with_kogral(mut self) -> Self { + self.kogral = KogralIntegration::new(); + self + } + + pub fn with_defaults(mut self, options: GenerationOptions) -> Self { + self.default_options = options; + self + } + + pub fn build(self) -> Result { + let chain = self.chain.ok_or(LlmError::NoProvidersAvailable)?; + + Ok(UnifiedClient { + chain, + cache: RequestCache::new(self.cache_config), + #[cfg(feature = "kogral")] + kogral: self.kogral, + default_options: self.default_options, + }) + } +} + +impl Default for UnifiedClientBuilder { + fn default() -> Self { + Self::new() + } +} + +impl UnifiedClient { + pub fn auto() -> Result { + UnifiedClientBuilder::new().auto_detect()?.build() + } + + pub fn builder() -> UnifiedClientBuilder { + UnifiedClientBuilder::new() + } + + pub async fn generate( + &self, + messages: &[Message], + options: Option<&GenerationOptions>, + ) -> Result { + let opts = options.unwrap_or(&self.default_options); + + self.cache + .get_or_generate(messages, opts, || self.chain.generate(messages, opts)) + .await + } + + #[cfg(feature = "kogral")] + pub async fn generate_with_kogral( + &self, + messages: &[Message], + options: Option<&GenerationOptions>, + language: Option<&str>, + domain: Option<&str>, + ) -> Result { + let opts = options.unwrap_or(&self.default_options); + + let enriched_messages = if let Some(kogral) = &self.kogral { + let mut ctx = serde_json::json!({}); + kogral + .enrich_context(&mut ctx, language, domain) + .await + .map_err(|e| LlmError::Context(e.to_string()))?; + + self.inject_kogral_context(messages, &ctx) + } else { + messages.to_vec() + }; + + self.generate(&enriched_messages, Some(opts)).await + } + + #[cfg(feature = "kogral")] + fn inject_kogral_context( + &self, + messages: &[Message], + kogral_ctx: &serde_json::Value, + ) -> Vec { + let mut result = Vec::with_capacity(messages.len()); + let mut system_found = false; + + for msg in messages { + if matches!(msg.role, crate::providers::Role::System) && !system_found { + let enhanced_content = format!( + "{}\n\n## Project Context (from Kogral)\n{}", + msg.content, + serde_json::to_string_pretty(kogral_ctx).unwrap_or_default() + ); + result.push(Message { + role: msg.role, + content: enhanced_content, + }); + system_found = true; + } else { + result.push(msg.clone()); + } + } + + result + } + + pub fn providers(&self) -> Vec { + self.chain.provider_info() + } + + pub fn clear_cache(&self) { + self.cache.clear(); + } +} diff --git a/crates/stratum-llm/src/credentials/claude_cli.rs b/crates/stratum-llm/src/credentials/claude_cli.rs new file mode 100644 index 0000000..0e4ff61 --- /dev/null +++ b/crates/stratum-llm/src/credentials/claude_cli.rs @@ -0,0 +1,68 @@ +#[cfg(feature = "claude-cli")] +use crate::credentials::detector::DetectedCredential; +#[cfg(feature = "claude-cli")] +use crate::providers::CredentialSource; + +#[cfg(feature = "claude-cli")] +impl crate::credentials::CredentialDetector { + pub fn detect_claude_cli(&self) -> Option { + let config_dir = dirs::config_dir()?; + + let possible_paths = [ + config_dir.join("claude").join("credentials.json"), + config_dir.join("claude-cli").join("auth.json"), + config_dir.join("anthropic").join("credentials.json"), + ]; + + for path in &possible_paths { + if let Some(cred) = Self::try_read_claude_credentials(path) { + return Some(cred); + } + } + + None + } + + fn try_read_claude_credentials(path: &std::path::Path) -> Option { + if !path.exists() { + return None; + } + + let content = std::fs::read_to_string(path).ok()?; + let json: serde_json::Value = serde_json::from_str(&content).ok()?; + + let token = json.get("access_token")?; + if !token.is_string() { + return None; + } + + if Self::is_token_expired(&json) { + tracing::debug!("Claude CLI token expired"); + return None; + } + + Some(DetectedCredential { + provider: "anthropic".to_string(), + source: CredentialSource::Cli { + path: path.to_path_buf(), + }, + is_subscription: true, + }) + } + + fn is_token_expired(json: &serde_json::Value) -> bool { + let Some(expires) = json.get("expires_at") else { + return false; + }; + + let Some(exp_str) = expires.as_str() else { + return false; + }; + + let Ok(exp) = chrono::DateTime::parse_from_rfc3339(exp_str) else { + return false; + }; + + exp < chrono::Utc::now() + } +} diff --git a/crates/stratum-llm/src/credentials/detector.rs b/crates/stratum-llm/src/credentials/detector.rs new file mode 100644 index 0000000..bdcf6f9 --- /dev/null +++ b/crates/stratum-llm/src/credentials/detector.rs @@ -0,0 +1,130 @@ +use crate::providers::CredentialSource; + +#[derive(Debug, Clone)] +pub struct DetectedCredential { + pub provider: String, + pub source: CredentialSource, + pub is_subscription: bool, +} + +pub struct CredentialDetector { + check_cli: bool, + check_env: bool, +} + +impl CredentialDetector { + pub fn new() -> Self { + Self { + check_cli: true, + check_env: true, + } + } + + pub fn without_cli(mut self) -> Self { + self.check_cli = false; + self + } + + pub fn detect_all(&self) -> Vec { + let mut credentials = Vec::new(); + + if self.check_cli { + #[cfg(feature = "claude-cli")] + if let Some(cred) = self.detect_claude_cli() { + credentials.push(cred); + } + } + + if self.check_env { + if let Some(cred) = self.detect_anthropic_env() { + credentials.push(cred); + } + if let Some(cred) = self.detect_openai_env() { + credentials.push(cred); + } + if let Some(cred) = self.detect_deepseek_env() { + credentials.push(cred); + } + } + + if let Some(cred) = self.detect_ollama() { + credentials.push(cred); + } + + credentials + } + + pub fn detect_for_provider(&self, provider: &str) -> Option { + match provider { + "anthropic" | "claude" => { + #[cfg(feature = "claude-cli")] + if self.check_cli { + if let Some(cred) = self.detect_claude_cli() { + return Some(cred); + } + } + self.detect_anthropic_env() + } + "openai" => self.detect_openai_env(), + "deepseek" => self.detect_deepseek_env(), + "ollama" => self.detect_ollama(), + _ => None, + } + } + + pub fn detect_anthropic_env(&self) -> Option { + if std::env::var("ANTHROPIC_API_KEY").is_ok() { + Some(DetectedCredential { + provider: "anthropic".to_string(), + source: CredentialSource::EnvVar { + name: "ANTHROPIC_API_KEY".to_string(), + }, + is_subscription: false, + }) + } else { + None + } + } + + pub fn detect_openai_env(&self) -> Option { + if std::env::var("OPENAI_API_KEY").is_ok() { + Some(DetectedCredential { + provider: "openai".to_string(), + source: CredentialSource::EnvVar { + name: "OPENAI_API_KEY".to_string(), + }, + is_subscription: false, + }) + } else { + None + } + } + + pub fn detect_deepseek_env(&self) -> Option { + if std::env::var("DEEPSEEK_API_KEY").is_ok() { + Some(DetectedCredential { + provider: "deepseek".to_string(), + source: CredentialSource::EnvVar { + name: "DEEPSEEK_API_KEY".to_string(), + }, + is_subscription: false, + }) + } else { + None + } + } + + pub fn detect_ollama(&self) -> Option { + Some(DetectedCredential { + provider: "ollama".to_string(), + source: CredentialSource::None, + is_subscription: false, + }) + } +} + +impl Default for CredentialDetector { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/stratum-llm/src/credentials/mod.rs b/crates/stratum-llm/src/credentials/mod.rs new file mode 100644 index 0000000..26edbe0 --- /dev/null +++ b/crates/stratum-llm/src/credentials/mod.rs @@ -0,0 +1,6 @@ +pub mod detector; + +#[cfg(feature = "claude-cli")] +pub mod claude_cli; + +pub use detector::{CredentialDetector, DetectedCredential}; diff --git a/crates/stratum-llm/src/error.rs b/crates/stratum-llm/src/error.rs new file mode 100644 index 0000000..638463a --- /dev/null +++ b/crates/stratum-llm/src/error.rs @@ -0,0 +1,47 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum LlmError { + #[error("No providers available")] + NoProvidersAvailable, + + #[error("Missing credential: {0}")] + MissingCredential(String), + + #[error("Network error: {0}")] + Network(String), + + #[error("API error: {0}")] + Api(String), + + #[error("Rate limited: {0}")] + RateLimit(String), + + #[error("Provider unavailable: {0}")] + Unavailable(String), + + #[error("Request timeout")] + Timeout, + + #[error("Parse error: {0}")] + Parse(String), + + #[error("Context error: {0}")] + Context(String), + + #[error("Circuit breaker open for provider")] + CircuitOpen, +} + +impl LlmError { + pub fn is_rate_limit(&self) -> bool { + matches!(self, Self::RateLimit(_)) + } + + pub fn is_retriable(&self) -> bool { + matches!( + self, + Self::Network(_) | Self::RateLimit(_) | Self::Timeout | Self::Unavailable(_) + ) + } +} diff --git a/crates/stratum-llm/src/kogral/integration.rs b/crates/stratum-llm/src/kogral/integration.rs new file mode 100644 index 0000000..d8aee6e --- /dev/null +++ b/crates/stratum-llm/src/kogral/integration.rs @@ -0,0 +1,216 @@ +#[cfg(feature = "kogral")] +use std::path::PathBuf; + +#[cfg(feature = "kogral")] +pub struct KogralIntegration { + kogral_path: PathBuf, +} + +#[cfg(feature = "kogral")] +impl KogralIntegration { + pub fn new() -> Option { + let possible_paths = [ + dirs::home_dir()?.join(".kogral"), + PathBuf::from("/Users/Akasha/Development/kogral/.kogral"), + ]; + + for path in &possible_paths { + if path.exists() { + return Some(Self { + kogral_path: path.clone(), + }); + } + } + + None + } + + pub fn with_path(path: impl Into) -> Self { + Self { + kogral_path: path.into(), + } + } + + pub async fn get_guidelines(&self, language: &str) -> Result, KogralError> { + let guidelines_dir = self.kogral_path.join("default"); + let mut guidelines = Vec::new(); + + if let Ok(entries) = std::fs::read_dir(&guidelines_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().is_none_or(|e| e != "md") { + continue; + } + + let Ok(content) = std::fs::read_to_string(&path) else { + continue; + }; + + if let Some(guideline) = Self::parse_guideline(&content, language) { + guidelines.push(guideline); + } + } + } + + Ok(guidelines) + } + + pub async fn get_patterns(&self, domain: &str) -> Result, KogralError> { + let patterns_dir = self.kogral_path.join("default"); + let mut patterns = Vec::new(); + + if let Ok(entries) = std::fs::read_dir(&patterns_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().is_none_or(|e| e != "md") { + continue; + } + + let Ok(content) = std::fs::read_to_string(&path) else { + continue; + }; + + if let Some(pattern) = Self::parse_pattern(&content, domain) { + patterns.push(pattern); + } + } + } + + Ok(patterns) + } + + pub async fn enrich_context( + &self, + context: &mut serde_json::Value, + language: Option<&str>, + domain: Option<&str>, + ) -> Result<(), KogralError> { + let mut kogral_context = serde_json::json!({}); + + if let Some(lang) = language { + let guidelines = self.get_guidelines(lang).await?; + if !guidelines.is_empty() { + kogral_context["guidelines"] = serde_json::to_value(&guidelines)?; + } + } + + if let Some(dom) = domain { + let patterns = self.get_patterns(dom).await?; + if !patterns.is_empty() { + kogral_context["patterns"] = serde_json::to_value(&patterns)?; + } + } + + if let Some(obj) = context.as_object_mut() { + obj.insert("kogral".to_string(), kogral_context); + } + + Ok(()) + } + + fn parse_guideline(content: &str, language: &str) -> Option { + let (frontmatter, body) = Self::split_frontmatter(content)?; + let meta: serde_yaml::Value = serde_yaml::from_str(&frontmatter).ok()?; + + let node_type = meta.get("node_type")?.as_str()?; + if node_type != "guideline" { + return None; + } + + let tags: Vec = meta + .get("tags")? + .as_sequence()? + .iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect(); + + if !tags.iter().any(|t| t.eq_ignore_ascii_case(language)) { + return None; + } + + Some(Guideline { + title: meta.get("title")?.as_str()?.to_string(), + content: body.to_string(), + tags, + }) + } + + fn parse_pattern(content: &str, domain: &str) -> Option { + let (frontmatter, body) = Self::split_frontmatter(content)?; + let meta: serde_yaml::Value = serde_yaml::from_str(&frontmatter).ok()?; + + let node_type = meta.get("node_type")?.as_str()?; + if node_type != "pattern" { + return None; + } + + let tags: Vec = meta + .get("tags")? + .as_sequence()? + .iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect(); + + if !tags.iter().any(|t| t.eq_ignore_ascii_case(domain)) { + return None; + } + + Some(Pattern { + title: meta.get("title")?.as_str()?.to_string(), + content: body.to_string(), + tags, + }) + } + + fn split_frontmatter(content: &str) -> Option<(String, String)> { + let content = content.trim(); + if !content.starts_with("---") { + return None; + } + + let after_first = &content[3..]; + let end = after_first.find("---")?; + let frontmatter = after_first[..end].trim().to_string(); + let body = after_first[end + 3..].trim().to_string(); + + Some((frontmatter, body)) + } +} + +#[cfg(feature = "kogral")] +impl Default for KogralIntegration { + fn default() -> Self { + Self::new().unwrap_or_else(|| Self { + kogral_path: PathBuf::from(".kogral"), + }) + } +} + +#[cfg(feature = "kogral")] +#[derive(Debug, Clone, serde::Serialize)] +pub struct Guideline { + pub title: String, + pub content: String, + pub tags: Vec, +} + +#[cfg(feature = "kogral")] +#[derive(Debug, Clone, serde::Serialize)] +pub struct Pattern { + pub title: String, + pub content: String, + pub tags: Vec, +} + +#[cfg(feature = "kogral")] +#[derive(Debug, thiserror::Error)] +pub enum KogralError { + #[error("Kogral not found")] + NotFound, + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Parse error: {0}")] + Parse(String), + #[error("Serialization error: {0}")] + Serialization(#[from] serde_json::Error), +} diff --git a/crates/stratum-llm/src/kogral/mod.rs b/crates/stratum-llm/src/kogral/mod.rs new file mode 100644 index 0000000..be593e2 --- /dev/null +++ b/crates/stratum-llm/src/kogral/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "kogral")] +pub mod integration; + +#[cfg(feature = "kogral")] +pub use integration::{Guideline, KogralError, KogralIntegration, Pattern}; diff --git a/crates/stratum-llm/src/lib.rs b/crates/stratum-llm/src/lib.rs new file mode 100644 index 0000000..83d7448 --- /dev/null +++ b/crates/stratum-llm/src/lib.rs @@ -0,0 +1,64 @@ +//! Unified LLM abstraction with CLI detection, fallback, and caching +//! +//! # Features +//! +//! - **Credential auto-detection**: Finds CLI credentials (Claude, OpenAI) and +//! API keys +//! - **Provider fallback**: Automatic failover with circuit breaker pattern +//! - **Smart caching**: xxHash-based deduplication reduces duplicate API calls +//! - **Kogral integration**: Inject project context from knowledge base +//! - **Cost tracking**: Transparent cost estimation across providers +//! +//! # Quick Start +//! +//! ```no_run +//! use stratum_llm::{UnifiedClient, Message, Role}; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let client = UnifiedClient::auto()?; +//! +//! let messages = vec![ +//! Message { +//! role: Role::User, +//! content: "What is Rust?".to_string(), +//! } +//! ]; +//! +//! let response = client.generate(&messages, None).await?; +//! println!("{}", response.content); +//! +//! Ok(()) +//! } +//! ``` + +pub mod cache; +pub mod chain; +pub mod client; +pub mod credentials; +pub mod error; +pub mod kogral; +pub mod metrics; +pub mod providers; + +pub use cache::{CacheConfig, RequestCache}; +pub use chain::{CircuitBreakerConfig, FallbackStrategy, ProviderChain}; +pub use client::{UnifiedClient, UnifiedClientBuilder}; +pub use credentials::{CredentialDetector, DetectedCredential}; +pub use error::LlmError; +#[cfg(feature = "kogral")] +pub use kogral::{Guideline, KogralIntegration, Pattern}; +#[cfg(feature = "metrics")] +pub use metrics::LlmMetrics; +#[cfg(feature = "anthropic")] +pub use providers::AnthropicProvider; +#[cfg(feature = "deepseek")] +pub use providers::DeepSeekProvider; +#[cfg(feature = "ollama")] +pub use providers::OllamaProvider; +#[cfg(feature = "openai")] +pub use providers::OpenAiProvider; +pub use providers::{ + ConfiguredProvider, CredentialSource, GenerationOptions, GenerationResponse, LlmProvider, + Message, Role, +}; diff --git a/crates/stratum-llm/src/metrics.rs b/crates/stratum-llm/src/metrics.rs new file mode 100644 index 0000000..98afb49 --- /dev/null +++ b/crates/stratum-llm/src/metrics.rs @@ -0,0 +1,74 @@ +#[cfg(feature = "metrics")] +use prometheus::{Counter, Histogram, IntGauge, Registry}; + +#[cfg(feature = "metrics")] +pub struct LlmMetrics { + pub requests_total: Counter, + pub requests_success: Counter, + pub requests_failed: Counter, + pub cache_hits: Counter, + pub cache_misses: Counter, + pub circuit_opens: Counter, + pub fallbacks: Counter, + pub latency_seconds: Histogram, + pub cost_cents: Counter, + pub active_circuits_open: IntGauge, +} + +#[cfg(feature = "metrics")] +impl LlmMetrics { + pub fn new() -> Self { + Self { + requests_total: Counter::new("stratum_llm_requests_total", "Total LLM requests") + .unwrap(), + requests_success: Counter::new( + "stratum_llm_requests_success_total", + "Successful LLM requests", + ) + .unwrap(), + requests_failed: Counter::new( + "stratum_llm_requests_failed_total", + "Failed LLM requests", + ) + .unwrap(), + cache_hits: Counter::new("stratum_llm_cache_hits_total", "Cache hits").unwrap(), + cache_misses: Counter::new("stratum_llm_cache_misses_total", "Cache misses").unwrap(), + circuit_opens: Counter::new("stratum_llm_circuit_opens_total", "Circuit breaker opens") + .unwrap(), + fallbacks: Counter::new("stratum_llm_fallbacks_total", "Provider fallbacks").unwrap(), + latency_seconds: Histogram::with_opts( + prometheus::HistogramOpts::new("stratum_llm_latency_seconds", "Request latency") + .buckets(vec![0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0]), + ) + .unwrap(), + cost_cents: Counter::new("stratum_llm_cost_cents_total", "Total cost in cents") + .unwrap(), + active_circuits_open: IntGauge::new( + "stratum_llm_circuits_open", + "Currently open circuit breakers", + ) + .unwrap(), + } + } + + pub fn register(&self, registry: &Registry) -> Result<(), prometheus::Error> { + registry.register(Box::new(self.requests_total.clone()))?; + registry.register(Box::new(self.requests_success.clone()))?; + registry.register(Box::new(self.requests_failed.clone()))?; + registry.register(Box::new(self.cache_hits.clone()))?; + registry.register(Box::new(self.cache_misses.clone()))?; + registry.register(Box::new(self.circuit_opens.clone()))?; + registry.register(Box::new(self.fallbacks.clone()))?; + registry.register(Box::new(self.latency_seconds.clone()))?; + registry.register(Box::new(self.cost_cents.clone()))?; + registry.register(Box::new(self.active_circuits_open.clone()))?; + Ok(()) + } +} + +#[cfg(feature = "metrics")] +impl Default for LlmMetrics { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/stratum-llm/src/providers/anthropic.rs b/crates/stratum-llm/src/providers/anthropic.rs new file mode 100644 index 0000000..4e84314 --- /dev/null +++ b/crates/stratum-llm/src/providers/anthropic.rs @@ -0,0 +1,181 @@ +use async_trait::async_trait; + +use crate::error::LlmError; +use crate::providers::{ + GenerationOptions, GenerationResponse, LlmProvider, Message, StreamResponse, +}; + +#[cfg(feature = "anthropic")] +pub struct AnthropicProvider { + client: reqwest::Client, + api_key: String, + model: String, +} + +#[cfg(feature = "anthropic")] +impl AnthropicProvider { + const BASE_URL: &'static str = "https://api.anthropic.com/v1"; + const API_VERSION: &'static str = "2023-06-01"; + + pub fn new(api_key: impl Into, model: impl Into) -> Self { + Self { + client: reqwest::Client::new(), + api_key: api_key.into(), + model: model.into(), + } + } + + pub fn from_env(model: impl Into) -> Result { + let api_key = std::env::var("ANTHROPIC_API_KEY") + .map_err(|_| LlmError::MissingCredential("ANTHROPIC_API_KEY".to_string()))?; + Ok(Self::new(api_key, model)) + } + + pub fn sonnet() -> Result { + Self::from_env("claude-sonnet-4-5-20250929") + } + + pub fn opus() -> Result { + Self::from_env("claude-opus-4-5-20251101") + } + + pub fn haiku() -> Result { + Self::from_env("claude-haiku-4-5-20251001") + } +} + +#[cfg(feature = "anthropic")] +#[async_trait] +impl LlmProvider for AnthropicProvider { + fn name(&self) -> &str { + "anthropic" + } + + fn model(&self) -> &str { + &self.model + } + + async fn is_available(&self) -> bool { + true + } + + async fn generate( + &self, + messages: &[Message], + options: &GenerationOptions, + ) -> Result { + let start = std::time::Instant::now(); + + let (system, user_messages): (Vec<_>, Vec<_>) = messages + .iter() + .partition(|m| matches!(m.role, crate::providers::Role::System)); + + let system_content = system.first().map(|m| m.content.as_str()); + + let mut body = serde_json::json!({ + "model": self.model, + "messages": user_messages.iter().map(|m| { + serde_json::json!({ + "role": match m.role { + crate::providers::Role::User => "user", + crate::providers::Role::Assistant => "assistant", + crate::providers::Role::System => "user", + }, + "content": m.content, + }) + }).collect::>(), + "max_tokens": options.max_tokens.unwrap_or(4096), + }); + + if let Some(sys) = system_content { + body["system"] = serde_json::json!(sys); + } + if let Some(temp) = options.temperature { + body["temperature"] = serde_json::json!(temp); + } + if let Some(top_p) = options.top_p { + body["top_p"] = serde_json::json!(top_p); + } + if !options.stop_sequences.is_empty() { + body["stop_sequences"] = serde_json::json!(options.stop_sequences); + } + + let response = self + .client + .post(format!("{}/messages", Self::BASE_URL)) + .header("x-api-key", &self.api_key) + .header("anthropic-version", Self::API_VERSION) + .header("content-type", "application/json") + .json(&body) + .send() + .await + .map_err(|e| LlmError::Network(e.to_string()))?; + + if !response.status().is_success() { + let status = response.status(); + let text = response.text().await.unwrap_or_default(); + + if status.as_u16() == 429 { + return Err(LlmError::RateLimit(text)); + } + return Err(LlmError::Api(format!("{}: {}", status, text))); + } + + let json: serde_json::Value = response + .json() + .await + .map_err(|e| LlmError::Parse(e.to_string()))?; + + let content = json["content"][0]["text"] + .as_str() + .unwrap_or("") + .to_string(); + + let input_tokens = json["usage"]["input_tokens"].as_u64().unwrap_or(0) as u32; + let output_tokens = json["usage"]["output_tokens"].as_u64().unwrap_or(0) as u32; + + Ok(GenerationResponse { + content, + model: self.model.clone(), + provider: "anthropic".to_string(), + input_tokens, + output_tokens, + cost_cents: self.estimate_cost(input_tokens, output_tokens), + latency_ms: start.elapsed().as_millis() as u64, + }) + } + + async fn stream( + &self, + _messages: &[Message], + _options: &GenerationOptions, + ) -> Result { + Err(LlmError::Unavailable( + "Streaming not yet implemented".to_string(), + )) + } + + fn estimate_cost(&self, input_tokens: u32, output_tokens: u32) -> f64 { + let input_cost = (input_tokens as f64 / 1_000_000.0) * self.cost_per_1m_input(); + let output_cost = (output_tokens as f64 / 1_000_000.0) * self.cost_per_1m_output(); + (input_cost + output_cost) * 100.0 + } + + fn cost_per_1m_input(&self) -> f64 { + match self.model.as_str() { + m if m.contains("opus") => 15.0, + m if m.contains("sonnet") => 3.0, + m if m.contains("haiku") => 1.0, + _ => 3.0, + } + } + + fn cost_per_1m_output(&self) -> f64 { + match self.model.as_str() { + m if m.contains("opus") => 75.0, + m if m.contains("sonnet") => 15.0, + m if m.contains("haiku") => 5.0, + _ => 15.0, + } + } +} diff --git a/crates/stratum-llm/src/providers/deepseek.rs b/crates/stratum-llm/src/providers/deepseek.rs new file mode 100644 index 0000000..3bd08d4 --- /dev/null +++ b/crates/stratum-llm/src/providers/deepseek.rs @@ -0,0 +1,147 @@ +use async_trait::async_trait; + +use crate::error::LlmError; +use crate::providers::{ + GenerationOptions, GenerationResponse, LlmProvider, Message, StreamResponse, +}; + +#[cfg(feature = "deepseek")] +pub struct DeepSeekProvider { + client: reqwest::Client, + api_key: String, + model: String, +} + +#[cfg(feature = "deepseek")] +impl DeepSeekProvider { + const BASE_URL: &'static str = "https://api.deepseek.com/v1"; + + pub fn new(api_key: impl Into, model: impl Into) -> Self { + Self { + client: reqwest::Client::new(), + api_key: api_key.into(), + model: model.into(), + } + } + + pub fn from_env(model: impl Into) -> Result { + let api_key = std::env::var("DEEPSEEK_API_KEY") + .map_err(|_| LlmError::MissingCredential("DEEPSEEK_API_KEY".to_string()))?; + Ok(Self::new(api_key, model)) + } + + pub fn coder() -> Result { + Self::from_env("deepseek-coder") + } + + pub fn chat() -> Result { + Self::from_env("deepseek-chat") + } +} + +#[cfg(feature = "deepseek")] +#[async_trait] +impl LlmProvider for DeepSeekProvider { + fn name(&self) -> &str { + "deepseek" + } + + fn model(&self) -> &str { + &self.model + } + + async fn is_available(&self) -> bool { + true + } + + async fn generate( + &self, + messages: &[Message], + options: &GenerationOptions, + ) -> Result { + let start = std::time::Instant::now(); + + let body = serde_json::json!({ + "model": self.model, + "messages": messages.iter().map(|m| { + serde_json::json!({ + "role": match m.role { + crate::providers::Role::System => "system", + crate::providers::Role::User => "user", + crate::providers::Role::Assistant => "assistant", + }, + "content": m.content, + }) + }).collect::>(), + "max_tokens": options.max_tokens.unwrap_or(4096), + "temperature": options.temperature.unwrap_or(0.7), + }); + + let response = self + .client + .post(format!("{}/chat/completions", Self::BASE_URL)) + .header("Authorization", format!("Bearer {}", self.api_key)) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + .map_err(|e| LlmError::Network(e.to_string()))?; + + if !response.status().is_success() { + let status = response.status(); + let text = response.text().await.unwrap_or_default(); + + if status.as_u16() == 429 { + return Err(LlmError::RateLimit(text)); + } + return Err(LlmError::Api(format!("{}: {}", status, text))); + } + + let json: serde_json::Value = response + .json() + .await + .map_err(|e| LlmError::Parse(e.to_string()))?; + + let content = json["choices"][0]["message"]["content"] + .as_str() + .unwrap_or("") + .to_string(); + + let input_tokens = json["usage"]["prompt_tokens"].as_u64().unwrap_or(0) as u32; + let output_tokens = json["usage"]["completion_tokens"].as_u64().unwrap_or(0) as u32; + + Ok(GenerationResponse { + content, + model: self.model.clone(), + provider: "deepseek".to_string(), + input_tokens, + output_tokens, + cost_cents: self.estimate_cost(input_tokens, output_tokens), + latency_ms: start.elapsed().as_millis() as u64, + }) + } + + async fn stream( + &self, + _messages: &[Message], + _options: &GenerationOptions, + ) -> Result { + Err(LlmError::Unavailable( + "Streaming not yet implemented".to_string(), + )) + } + + fn estimate_cost(&self, input_tokens: u32, output_tokens: u32) -> f64 { + let input_cost = (input_tokens as f64 / 1_000_000.0) * self.cost_per_1m_input(); + let output_cost = (output_tokens as f64 / 1_000_000.0) * self.cost_per_1m_output(); + (input_cost + output_cost) * 100.0 + } + + fn cost_per_1m_input(&self) -> f64 { + 0.14 + } + + fn cost_per_1m_output(&self) -> f64 { + 0.28 + } +} diff --git a/crates/stratum-llm/src/providers/mod.rs b/crates/stratum-llm/src/providers/mod.rs new file mode 100644 index 0000000..8a5b91b --- /dev/null +++ b/crates/stratum-llm/src/providers/mod.rs @@ -0,0 +1,23 @@ +pub mod traits; + +#[cfg(feature = "anthropic")] +pub mod anthropic; +#[cfg(feature = "deepseek")] +pub mod deepseek; +#[cfg(feature = "ollama")] +pub mod ollama; +#[cfg(feature = "openai")] +pub mod openai; + +#[cfg(feature = "anthropic")] +pub use anthropic::AnthropicProvider; +#[cfg(feature = "deepseek")] +pub use deepseek::DeepSeekProvider; +#[cfg(feature = "ollama")] +pub use ollama::OllamaProvider; +#[cfg(feature = "openai")] +pub use openai::OpenAiProvider; +pub use traits::{ + ConfiguredProvider, CredentialSource, GenerationOptions, GenerationResponse, LlmProvider, + Message, Role, StreamChunk, StreamResponse, +}; diff --git a/crates/stratum-llm/src/providers/ollama.rs b/crates/stratum-llm/src/providers/ollama.rs new file mode 100644 index 0000000..8438f1b --- /dev/null +++ b/crates/stratum-llm/src/providers/ollama.rs @@ -0,0 +1,159 @@ +use async_trait::async_trait; + +use crate::error::LlmError; +use crate::providers::{ + GenerationOptions, GenerationResponse, LlmProvider, Message, StreamResponse, +}; + +#[cfg(feature = "ollama")] +pub struct OllamaProvider { + client: reqwest::Client, + base_url: String, + model: String, +} + +#[cfg(feature = "ollama")] +impl OllamaProvider { + pub fn new(base_url: impl Into, model: impl Into) -> Self { + Self { + client: reqwest::Client::new(), + base_url: base_url.into(), + model: model.into(), + } + } + + pub fn from_env(model: impl Into) -> Self { + let base_url = + std::env::var("OLLAMA_HOST").unwrap_or_else(|_| "http://localhost:11434".to_string()); + Self::new(base_url, model) + } + + pub fn llama3() -> Self { + Self::from_env("llama3") + } + + pub fn codellama() -> Self { + Self::from_env("codellama") + } + + pub fn mistral() -> Self { + Self::from_env("mistral") + } +} + +#[cfg(feature = "ollama")] +impl Default for OllamaProvider { + fn default() -> Self { + Self::llama3() + } +} + +#[cfg(feature = "ollama")] +#[async_trait] +impl LlmProvider for OllamaProvider { + fn name(&self) -> &str { + "ollama" + } + + fn model(&self) -> &str { + &self.model + } + + async fn is_available(&self) -> bool { + self.client + .get(format!("{}/api/tags", self.base_url)) + .send() + .await + .is_ok() + } + + async fn generate( + &self, + messages: &[Message], + options: &GenerationOptions, + ) -> Result { + let start = std::time::Instant::now(); + + let prompt = messages + .iter() + .map(|m| match m.role { + crate::providers::Role::System => format!("System: {}", m.content), + crate::providers::Role::User => format!("User: {}", m.content), + crate::providers::Role::Assistant => format!("Assistant: {}", m.content), + }) + .collect::>() + .join("\n\n"); + + let mut body = serde_json::json!({ + "model": self.model, + "prompt": prompt, + "stream": false, + }); + + if let Some(temp) = options.temperature { + body["temperature"] = serde_json::json!(temp); + } + if let Some(top_p) = options.top_p { + body["top_p"] = serde_json::json!(top_p); + } + if !options.stop_sequences.is_empty() { + body["stop"] = serde_json::json!(options.stop_sequences); + } + + let response = self + .client + .post(format!("{}/api/generate", self.base_url)) + .json(&body) + .send() + .await + .map_err(|e| LlmError::Network(e.to_string()))?; + + if !response.status().is_success() { + let status = response.status(); + let text = response.text().await.unwrap_or_default(); + return Err(LlmError::Api(format!("{}: {}", status, text))); + } + + let json: serde_json::Value = response + .json() + .await + .map_err(|e| LlmError::Parse(e.to_string()))?; + + let content = json["response"].as_str().unwrap_or("").to_string(); + + let input_tokens = prompt.len() as u32 / 4; + let output_tokens = content.len() as u32 / 4; + + Ok(GenerationResponse { + content, + model: self.model.clone(), + provider: "ollama".to_string(), + input_tokens, + output_tokens, + cost_cents: 0.0, + latency_ms: start.elapsed().as_millis() as u64, + }) + } + + async fn stream( + &self, + _messages: &[Message], + _options: &GenerationOptions, + ) -> Result { + Err(LlmError::Unavailable( + "Streaming not yet implemented".to_string(), + )) + } + + fn estimate_cost(&self, _input_tokens: u32, _output_tokens: u32) -> f64 { + 0.0 + } + + fn cost_per_1m_input(&self) -> f64 { + 0.0 + } + + fn cost_per_1m_output(&self) -> f64 { + 0.0 + } +} diff --git a/crates/stratum-llm/src/providers/openai.rs b/crates/stratum-llm/src/providers/openai.rs new file mode 100644 index 0000000..556a195 --- /dev/null +++ b/crates/stratum-llm/src/providers/openai.rs @@ -0,0 +1,161 @@ +use async_trait::async_trait; + +use crate::error::LlmError; +use crate::providers::{ + GenerationOptions, GenerationResponse, LlmProvider, Message, StreamResponse, +}; + +#[cfg(feature = "openai")] +pub struct OpenAiProvider { + client: reqwest::Client, + api_key: String, + model: String, +} + +#[cfg(feature = "openai")] +impl OpenAiProvider { + const BASE_URL: &'static str = "https://api.openai.com/v1"; + + pub fn new(api_key: impl Into, model: impl Into) -> Self { + Self { + client: reqwest::Client::new(), + api_key: api_key.into(), + model: model.into(), + } + } + + pub fn from_env(model: impl Into) -> Result { + let api_key = std::env::var("OPENAI_API_KEY") + .map_err(|_| LlmError::MissingCredential("OPENAI_API_KEY".to_string()))?; + Ok(Self::new(api_key, model)) + } + + pub fn gpt4o() -> Result { + Self::from_env("gpt-4o") + } + + pub fn gpt4_turbo() -> Result { + Self::from_env("gpt-4-turbo") + } + + pub fn gpt35_turbo() -> Result { + Self::from_env("gpt-3.5-turbo") + } +} + +#[cfg(feature = "openai")] +#[async_trait] +impl LlmProvider for OpenAiProvider { + fn name(&self) -> &str { + "openai" + } + + fn model(&self) -> &str { + &self.model + } + + async fn is_available(&self) -> bool { + true + } + + async fn generate( + &self, + messages: &[Message], + options: &GenerationOptions, + ) -> Result { + let start = std::time::Instant::now(); + + let body = serde_json::json!({ + "model": self.model, + "messages": messages.iter().map(|m| { + serde_json::json!({ + "role": match m.role { + crate::providers::Role::System => "system", + crate::providers::Role::User => "user", + crate::providers::Role::Assistant => "assistant", + }, + "content": m.content, + }) + }).collect::>(), + "max_tokens": options.max_tokens.unwrap_or(4096), + "temperature": options.temperature.unwrap_or(0.7), + }); + + let response = self + .client + .post(format!("{}/chat/completions", Self::BASE_URL)) + .header("Authorization", format!("Bearer {}", self.api_key)) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + .map_err(|e| LlmError::Network(e.to_string()))?; + + if !response.status().is_success() { + let status = response.status(); + let text = response.text().await.unwrap_or_default(); + + if status.as_u16() == 429 { + return Err(LlmError::RateLimit(text)); + } + return Err(LlmError::Api(format!("{}: {}", status, text))); + } + + let json: serde_json::Value = response + .json() + .await + .map_err(|e| LlmError::Parse(e.to_string()))?; + + let content = json["choices"][0]["message"]["content"] + .as_str() + .unwrap_or("") + .to_string(); + + let input_tokens = json["usage"]["prompt_tokens"].as_u64().unwrap_or(0) as u32; + let output_tokens = json["usage"]["completion_tokens"].as_u64().unwrap_or(0) as u32; + + Ok(GenerationResponse { + content, + model: self.model.clone(), + provider: "openai".to_string(), + input_tokens, + output_tokens, + cost_cents: self.estimate_cost(input_tokens, output_tokens), + latency_ms: start.elapsed().as_millis() as u64, + }) + } + + async fn stream( + &self, + _messages: &[Message], + _options: &GenerationOptions, + ) -> Result { + Err(LlmError::Unavailable( + "Streaming not yet implemented".to_string(), + )) + } + + fn estimate_cost(&self, input_tokens: u32, output_tokens: u32) -> f64 { + let input_cost = (input_tokens as f64 / 1_000_000.0) * self.cost_per_1m_input(); + let output_cost = (output_tokens as f64 / 1_000_000.0) * self.cost_per_1m_output(); + (input_cost + output_cost) * 100.0 + } + + fn cost_per_1m_input(&self) -> f64 { + match self.model.as_str() { + "gpt-4o" => 5.0, + "gpt-4-turbo" => 10.0, + "gpt-3.5-turbo" => 0.5, + _ => 5.0, + } + } + + fn cost_per_1m_output(&self) -> f64 { + match self.model.as_str() { + "gpt-4o" => 15.0, + "gpt-4-turbo" => 30.0, + "gpt-3.5-turbo" => 1.5, + _ => 15.0, + } + } +} diff --git a/crates/stratum-llm/src/providers/traits.rs b/crates/stratum-llm/src/providers/traits.rs new file mode 100644 index 0000000..a3211ed --- /dev/null +++ b/crates/stratum-llm/src/providers/traits.rs @@ -0,0 +1,95 @@ +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Message { + pub role: Role, + pub content: String, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Role { + System, + User, + Assistant, +} + +#[derive(Debug, Clone, Default)] +pub struct GenerationOptions { + pub temperature: Option, + pub max_tokens: Option, + pub top_p: Option, + pub stop_sequences: Vec, +} + +#[derive(Debug, Clone)] +pub struct GenerationResponse { + pub content: String, + pub model: String, + pub provider: String, + pub input_tokens: u32, + pub output_tokens: u32, + pub cost_cents: f64, + pub latency_ms: u64, +} + +pub type StreamChunk = String; +pub type StreamResponse = std::pin::Pin< + Box> + Send>, +>; + +#[async_trait] +pub trait LlmProvider: Send + Sync { + /// Provider name (e.g., "anthropic", "openai", "ollama") + fn name(&self) -> &str; + + /// Model identifier + fn model(&self) -> &str; + + /// Check if provider is available and configured + async fn is_available(&self) -> bool; + + /// Generate a completion + async fn generate( + &self, + messages: &[Message], + options: &GenerationOptions, + ) -> Result; + + /// Stream a completion (future work) + async fn stream( + &self, + messages: &[Message], + options: &GenerationOptions, + ) -> Result; + + /// Estimate cost for a request (before sending) + fn estimate_cost(&self, input_tokens: u32, output_tokens: u32) -> f64; + + /// Cost per 1M input tokens in cents + fn cost_per_1m_input(&self) -> f64; + + /// Cost per 1M output tokens in cents + fn cost_per_1m_output(&self) -> f64; +} + +/// Credential source for a provider +#[derive(Debug, Clone)] +pub enum CredentialSource { + /// CLI tool credentials (subscription-based, no per-token cost) + Cli { path: std::path::PathBuf }, + /// API key from environment variable + EnvVar { name: String }, + /// API key from config file + ConfigFile { path: std::path::PathBuf }, + /// No credentials needed (local provider) + None, +} + +/// Provider with credential metadata +pub struct ConfiguredProvider { + pub provider: Box, + pub credential_source: CredentialSource, + pub priority: u32, +} diff --git a/docs/README.md b/docs/README.md index f5d1c83..86c192c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,6 +32,15 @@ Infrastructure automation and deployment tools. See [Operations Portfolio Docs](en/ops/) for technical details. +### Architecture + +Cross-cutting architectural decisions documented as ADRs. + +- [ADR-001: Stratum-Embeddings](en/architecture/adrs/001-stratum-embeddings.md) - Unified embedding library +- [ADR-002: Stratum-LLM](en/architecture/adrs/002-stratum-llm.md) - Unified LLM provider library + +See [Architecture Docs](en/architecture/) for all ADRs. + ## Quick Start 1. Choose your language: [English](en/) | [Español](es/) @@ -47,3 +56,4 @@ Each language directory contains: - `stratiumiops-technical-specs.md` - Technical specifications - `ia/` - AI portfolio documentation - `ops/` - Operations portfolio documentation +- `architecture/` - Architecture documentation and ADRs diff --git a/docs/en/README.md b/docs/en/README.md index 631cf1d..ab15b4c 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -34,6 +34,16 @@ Infrastructure automation and deployment tools. See [ops/](ops/) directory for full operations portfolio documentation. +### Architecture + +Architectural decisions and ecosystem design. + +- [**ADRs**](architecture/adrs/) - Architecture Decision Records + - [ADR-001: Stratum-Embeddings](architecture/adrs/001-stratum-embeddings.md) - Unified embedding library + - [ADR-002: Stratum-LLM](architecture/adrs/002-stratum-llm.md) - Unified LLM provider library + +See [architecture/](architecture/) directory for full architecture documentation. + ## Navigation - [Back to root documentation](../) diff --git a/docs/en/architecture/README.md b/docs/en/architecture/README.md new file mode 100644 index 0000000..520e38d --- /dev/null +++ b/docs/en/architecture/README.md @@ -0,0 +1,30 @@ +# Architecture + +Architecture documentation for the STRATUMIOPS ecosystem. + +## Contents + +### ADRs (Architecture Decision Records) + +Documented architectural decisions following the ADR format: + +- [**ADR-001: Stratum-Embeddings**](adrs/001-stratum-embeddings.md) - Unified embedding library +- [**ADR-002: Stratum-LLM**](adrs/002-stratum-llm.md) - Unified LLM provider library + +## ADR Format + +Each ADR follows this structure: + +| Section | Description | +| --------------- | ------------------------------------------ | +| Status | Proposed, Accepted, Deprecated, Superseded | +| Context | Problem and current state | +| Decision | Chosen solution | +| Rationale | Why this solution | +| Consequences | Positive, negative, mitigations | +| Success Metrics | How to measure the outcome | + +## Navigation + +- [Back to main documentation](../) +- [Spanish version](../../es/architecture/) diff --git a/docs/en/architecture/adrs/001-stratum-embeddings.md b/docs/en/architecture/adrs/001-stratum-embeddings.md new file mode 100644 index 0000000..18a7aa7 --- /dev/null +++ b/docs/en/architecture/adrs/001-stratum-embeddings.md @@ -0,0 +1,279 @@ +# ADR-001: Stratum-Embeddings - Unified Embedding Library + +## Status + +**Proposed** + +## Context + +### Current State: Fragmented Implementations + +The ecosystem has 3 independent embedding implementations: + +| Project | Location | Providers | Caching | +| ------------ | ------------------------------------- | ----------------------------- | ------- | +| Kogral | `kogral-core/src/embeddings/` | fastembed, rig-core (partial) | No | +| Provisioning | `provisioning-rag/src/embeddings.rs` | OpenAI direct | No | +| Vapora | `vapora-llm-router/src/embeddings.rs` | OpenAI, HuggingFace, Ollama | No | + +### Identified Problems + +#### 1. Duplicated Code + +Each project reimplements: + +- HTTP client for OpenAI embeddings +- JSON response parsing +- Error handling +- Token estimation + +**Impact**: ~400 duplicated lines, inconsistent error handling. + +#### 2. No Caching + +Embeddings regenerated every time: + +```text +"What is Rust?" → OpenAI → 1536 dims → $0.00002 +"What is Rust?" → OpenAI → 1536 dims → $0.00002 (same result) +"What is Rust?" → OpenAI → 1536 dims → $0.00002 (same result) +``` + +**Impact**: Unnecessary costs, additional latency, more frequent rate limits. + +#### 3. No Fallback + +If OpenAI fails, everything fails. No fallback to local alternatives (fastembed, Ollama). + +**Impact**: Reduced availability, total dependency on one provider. + +#### 4. Silent Dimension Mismatch + +Different providers produce different dimensions: + +| Provider | Model | Dimensions | +| --------- | ---------------------- | ---------- | +| fastembed | bge-small-en | 384 | +| fastembed | bge-large-en | 1024 | +| OpenAI | text-embedding-3-small | 1536 | +| OpenAI | text-embedding-3-large | 3072 | +| Ollama | nomic-embed-text | 768 | + +**Impact**: Corrupt vector indices if provider changes. + +#### 5. No Metrics + +No visibility into usage, cache hit rate, latency per provider, or accumulated costs. + +## Decision + +Create `stratum-embeddings` as a unified crate that: + +1. **Unifies** implementations from Kogral, Provisioning, and Vapora +2. **Adds caching** to avoid recomputing identical embeddings +3. **Implements fallback** between providers (cloud → local) +4. **Clearly documents** dimensions and limitations per provider +5. **Exposes metrics** for observability +6. **Provides VectorStore trait** with LanceDB and SurrealDB backends based on project needs + +### Storage Backend Decision + +Each project chooses its vector storage backend based on priority: + +| Project | Backend | Priority | Justification | +| ------------ | --------- | -------------- | -------------------------------------------------- | +| Kogral | SurrealDB | Graph richness | Knowledge Graph needs unified graph+vector queries | +| Provisioning | LanceDB | Vector scale | RAG with millions of document chunks | +| Vapora | LanceDB | Vector scale | Execution traces, pattern matching at scale | + +#### Why SurrealDB for Kogral + +Kogral is a Knowledge Graph where relationships are the primary value. +With hybrid architecture (LanceDB vectors + SurrealDB graph), a typical query would require: + +1. LanceDB: vector search → candidate_ids +2. SurrealDB: graph filter on candidates → results +3. App layer: merge, re-rank, deduplication + +**Accepted trade-off**: SurrealDB has worse pure vector performance than LanceDB, +but Kogral's scale is limited by human curation of knowledge (typically 10K-100K concepts). + +#### Why LanceDB for Provisioning and Vapora + +| Aspect | SurrealDB | LanceDB | +| --------------- | ---------- | -------------------- | +| Storage format | Row-based | Columnar (Lance) | +| Vector index | HNSW (RAM) | IVF-PQ (disk-native) | +| Practical scale | Millions | Billions | +| Compression | ~1x | ~32x (PQ) | +| Zero-copy read | No | Yes | + +### Architecture + +```text +┌─────────────────────────────────────────────────────────────────┐ +│ stratum-embeddings │ +├─────────────────────────────────────────────────────────────────┤ +│ EmbeddingProvider trait │ +│ ├─ embed(text) → Vec │ +│ ├─ embed_batch(texts) → Vec> │ +│ ├─ dimensions() → usize │ +│ └─ is_local() → bool │ +│ │ +│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ +│ │ FastEmbed │ │ OpenAI │ │ Ollama │ │ +│ │ (local) │ │ (cloud) │ │ (local) │ │ +│ └───────────┘ └───────────┘ └───────────┘ │ +│ └────────────┬────────────┘ │ +│ ▼ │ +│ EmbeddingCache (memory/disk) │ +│ │ │ +│ ▼ │ +│ EmbeddingService │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ VectorStore trait │ │ +│ │ ├─ upsert(id, embedding, metadata) │ │ +│ │ ├─ search(embedding, limit, filter) → Vec │ │ +│ │ └─ delete(id) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ SurrealDbStore │ │ LanceDbStore │ │ +│ │ (Kogral) │ │ (Prov/Vapora) │ │ +│ └─────────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Rationale + +### Why Caching is Critical + +For a typical RAG system (10,000 chunks): + +- **Without cache**: Re-indexing and repeated queries multiply costs +- **With cache**: First indexing pays, rest are cache hits + +**Estimated savings**: 60-80% in embedding costs. + +### Why Fallback is Important + +| Scenario | Without Fallback | With Fallback | +| ----------------- | ---------------- | -------------------- | +| OpenAI rate limit | ERROR | → fastembed (local) | +| OpenAI downtime | ERROR | → Ollama (local) | +| No internet | ERROR | → fastembed (local) | + +### Why Local Providers First + +For development: fastembed loads local model (~100MB), no API keys required, no costs, works offline. + +For production: OpenAI for quality, fastembed as fallback. + +## Consequences + +### Positive + +1. Single source of truth for the entire ecosystem +2. 60-80% fewer embedding API calls (caching) +3. High availability with local providers (fallback) +4. Usage and cost metrics +5. Feature-gated: only compile what you need +6. Storage flexibility: VectorStore trait allows choosing backend per project + +### Negative + +1. **Dimension lock-in**: Changing provider requires re-indexing +2. **Cache invalidation**: Updated content may serve stale embeddings +3. **Model download**: fastembed downloads ~100MB on first use +4. **Storage lock-in per project**: Kogral tied to SurrealDB, others to LanceDB + +### Mitigations + +| Negative | Mitigation | +| ----------------- | ---------------------------------------------- | +| Dimension lock-in | Document clearly, warn on provider change | +| Stale cache | Configurable TTL, bypass option | +| Model download | Show progress, cache in ~/.cache/fastembed | +| Storage lock-in | Conscious decision based on project priorities | + +## Success Metrics + +| Metric | Current | Target | +| ------------------------- | ------- | ------ | +| Duplicate implementations | 3 | 1 | +| Cache hit rate | 0% | >60% | +| Fallback availability | 0% | 100% | +| Cost per 10K embeddings | ~$0.20 | ~$0.05 | + +## Provider Selection Guide + +### Development + +```rust +// Local, free, offline +let service = EmbeddingService::builder() + .with_provider(FastEmbedProvider::small()?) // 384 dims + .with_memory_cache() + .build()?; +``` + +### Production (Quality) + +```rust +// OpenAI with local fallback +let service = EmbeddingService::builder() + .with_provider(OpenAiEmbeddingProvider::large()?) // 3072 dims + .with_provider(FastEmbedProvider::large()?) // Fallback + .with_memory_cache() + .build()?; +``` + +### Production (Cost-Optimized) + +```rust +// OpenAI small with fallback +let service = EmbeddingService::builder() + .with_provider(OpenAiEmbeddingProvider::small()?) // 1536 dims + .with_provider(OllamaEmbeddingProvider::nomic()) // Fallback + .with_memory_cache() + .build()?; +``` + +## Dimension Compatibility Matrix + +| If using... | Can switch to... | CANNOT switch to... | +| ---------------------- | --------------------------- | ------------------- | +| fastembed small (384) | fastembed small, all-minilm | Any other | +| fastembed large (1024) | fastembed large | Any other | +| OpenAI small (1536) | OpenAI small, ada-002 | Any other | +| OpenAI large (3072) | OpenAI large | Any other | + +**Rule**: Only switch between models with the SAME dimensions. + +## Implementation Priority + +| Order | Feature | Reason | +| ----- | ----------------------- | -------------------------- | +| 1 | EmbeddingProvider trait | Foundation for everything | +| 2 | FastEmbed provider | Works without API keys | +| 3 | Memory cache | Biggest cost impact | +| 4 | VectorStore trait | Storage abstraction | +| 5 | SurrealDbStore | Kogral needs graph+vector | +| 6 | LanceDbStore | Provisioning/Vapora scale | +| 7 | OpenAI provider | Production | +| 8 | Ollama provider | Local fallback | +| 9 | Batch processing | Efficiency | +| 10 | Metrics | Observability | + +## References + +**Existing Implementations**: + +- Kogral: `kogral-core/src/embeddings/` +- Vapora: `vapora-llm-router/src/embeddings.rs` +- Provisioning: `provisioning/platform/crates/rag/src/embeddings.rs` + +**Target Location**: `stratumiops/crates/stratum-embeddings/` diff --git a/docs/en/architecture/adrs/002-stratum-llm.md b/docs/en/architecture/adrs/002-stratum-llm.md new file mode 100644 index 0000000..c992b8f --- /dev/null +++ b/docs/en/architecture/adrs/002-stratum-llm.md @@ -0,0 +1,279 @@ +# ADR-002: Stratum-LLM - Unified LLM Provider Library + +## Status + +**Proposed** + +## Context + +### Current State: Fragmented LLM Connections + +The stratumiops ecosystem has 4 projects with AI functionality, each with its own implementation: + +| Project | Implementation | Providers | Duplication | +| ------------ | -------------------------- | ---------------------- | ------------------- | +| Vapora | `typedialog-ai` (path dep) | Claude, OpenAI, Ollama | Shared base | +| TypeDialog | `typedialog-ai` (local) | Claude, OpenAI, Ollama | Defines abstraction | +| Provisioning | Custom `LlmClient` | Claude, OpenAI | 100% duplicated | +| Kogral | `rig-core` | Embeddings only | Different stack | + +### Identified Problems + +#### 1. Code Duplication + +Provisioning reimplements what TypeDialog already has: + +- reqwest HTTP client +- Headers: x-api-key, anthropic-version +- JSON body formatting +- Response parsing +- Error handling + +**Impact**: ~500 duplicated lines, bugs fixed in one place don't propagate. + +#### 2. API Keys Only, No CLI Detection + +No project detects credentials from official CLIs: + +```text +Claude CLI: ~/.config/claude/credentials.json +OpenAI CLI: ~/.config/openai/credentials.json +``` + +**Impact**: Users with Claude Pro/Max ($20-100/month) pay for API tokens when they could use their subscription. + +#### 3. No Automatic Fallback + +When a provider fails (rate limit, timeout), the request fails completely: + +```text +Actual: Request → Claude API → Rate Limit → ERROR +Desired: Request → Claude API → Rate Limit → OpenAI → Success +``` + +#### 4. No Circuit Breaker + +If Claude API is down, each request attempts to connect, fails, and propagates the error: + +```text +Request 1 → Claude → Timeout (30s) → Error +Request 2 → Claude → Timeout (30s) → Error +Request 3 → Claude → Timeout (30s) → Error +``` + +**Impact**: Accumulated latency, degraded UX. + +#### 5. No Caching + +Identical requests always go to the API: + +```text +"Explain this Rust error" → Claude → $0.003 +"Explain this Rust error" → Claude → $0.003 (same result) +``` + +**Impact**: Unnecessary costs, especially in development/testing. + +#### 6. Kogral Not Integrated + +Kogral has guidelines and patterns that could enrich LLM context, but there's no integration. + +## Decision + +Create `stratum-llm` as a unified crate that: + +1. **Consolidates** existing implementations from typedialog-ai and provisioning +2. **Detects** CLI credentials and subscriptions before using API keys +3. **Implements** automatic fallback with circuit breaker +4. **Adds** request caching to reduce costs +5. **Integrates** Kogral for context enrichment +6. **Is used** by all ecosystem projects + +### Architecture + +```text +┌─────────────────────────────────────────────────────────┐ +│ stratum-llm │ +├─────────────────────────────────────────────────────────┤ +│ CredentialDetector │ +│ ├─ Claude CLI → ~/.config/claude/ (subscription) │ +│ ├─ OpenAI CLI → ~/.config/openai/ │ +│ ├─ Env vars → *_API_KEY │ +│ └─ Ollama → localhost:11434 (free) │ +│ │ │ +│ ▼ │ +│ ProviderChain (ordered by priority) │ +│ [CLI/Sub] → [API] → [DeepSeek] → [Ollama] │ +│ │ │ │ │ │ +│ └──────────┴─────────┴───────────┘ │ +│ │ │ +│ CircuitBreaker per provider │ +│ │ │ +│ RequestCache │ +│ │ │ +│ KogralIntegration │ +│ │ │ +│ UnifiedClient │ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +## Rationale + +### Why Not Use Another External Crate + +| Alternative | Why Not | +| -------------- | ------------------------------------------ | +| kaccy-ai | Oriented toward blockchain/fraud detection | +| llm (crate) | Very basic, no circuit breaker or caching | +| langchain-rust | Python port, not idiomatic Rust | +| rig-core | Embeddings/RAG only, no chat completion | + +**Best option**: Build on typedialog-ai and add missing features. + +### Why CLI Detection is Important + +Cost analysis for typical user: + +| Scenario | Monthly Cost | +| ------------------------- | -------------------- | +| API only (current) | ~$840 | +| Claude Pro + API overflow | ~$20 + ~$200 = $220 | +| Claude Max + API overflow | ~$100 + ~$50 = $150 | + +**Potential savings**: 70-80% by detecting and using subscriptions first. + +### Why Circuit Breaker + +Without circuit breaker, a downed provider causes: + +- N requests × 30s timeout = N×30s total latency +- All resources occupied waiting for timeouts + +With circuit breaker: + +- First failure opens circuit +- Following requests fail immediately (fast fail) +- Fallback to another provider without waiting +- Circuit resets after cooldown + +### Why Caching + +For typical development: + +- Same questions repeated while iterating +- Testing executes same prompts multiple times + +Estimated cache hit rate: 15-30% in active development. + +### Why Kogral Integration + +Kogral has language guidelines, domain patterns, and ADRs. +Without integration the LLM generates generic code; +with integration it generates code following project conventions. + +## Consequences + +### Positive + +1. Single source of truth for LLM logic +2. CLI detection reduces costs 70-80% +3. Circuit breaker + fallback = high availability +4. 15-30% fewer requests in development (caching) +5. Kogral improves generation quality +6. Feature-gated: each feature is optional + +### Negative + +1. **Migration effort**: Refactor Vapora, TypeDialog, Provisioning +2. **New dependency**: Projects depend on stratumiops +3. **CLI auth complexity**: Different credential formats per version +4. **Cache invalidation**: Stale responses if not managed well + +### Mitigations + +| Negative | Mitigation | +| ------------------- | ------------------------------------------- | +| Migration effort | Re-export compatible API from typedialog-ai | +| New dependency | Local path dependency, not crates.io | +| CLI auth complexity | Version detection, fallback to API if fails | +| Cache invalidation | Configurable TTL, bypass option | + +## Success Metrics + +| Metric | Current | Target | +| ------------------------ | ------- | --------------- | +| Duplicated lines of code | ~500 | 0 | +| CLI credential detection | 0% | 100% | +| Fallback success rate | 0% | >90% | +| Cache hit rate | 0% | 15-30% | +| Latency (provider down) | 30s+ | <1s (fast fail) | + +## Cost Impact Analysis + +Based on real usage data ($840/month): + +| Scenario | Savings | +| -------------------------- | ------------------ | +| CLI detection (Claude Max) | ~$700/month | +| Caching (15% hit rate) | ~$50/month | +| DeepSeek fallback for code | ~$100/month | +| **Total potential** | **$500-700/month** | + +## Migration Strategy + +### Migration Phases + +1. Create stratum-llm with API compatible with typedialog-ai +2. typedialog-ai re-exports stratum-llm (backward compatible) +3. Vapora migrates to stratum-llm directly +4. Provisioning migrates its LlmClient to stratum-llm +5. Deprecate typedialog-ai, consolidate in stratum-llm + +### Feature Adoption + +| Feature | Adoption | +| --------------- | ----------------------------------------- | +| Basic providers | Immediate (direct replacement) | +| CLI detection | Optional, feature flag | +| Circuit breaker | Default on | +| Caching | Default on, configurable TTL | +| Kogral | Feature flag, requires Kogral installed | + +## Alternatives Considered + +### Alternative 1: Improve typedialog-ai In-Place + +**Pros**: No new crate required + +**Cons**: TypeDialog is a specific project, not shared infrastructure + +**Decision**: stratum-llm in stratumiops is better location for cross-project infrastructure. + +### Alternative 2: Use LiteLLM (Python) as Proxy + +**Pros**: Very complete, 100+ providers + +**Cons**: Python dependency, proxy latency, not Rust-native + +**Decision**: Keep pure Rust stack. + +### Alternative 3: Each Project Maintains Its Own Implementation + +**Pros**: Independence + +**Cons**: Duplication, inconsistency, bugs not shared + +**Decision**: Consolidation is better long-term. + +## References + +**Existing Implementations**: + +- TypeDialog: `typedialog/crates/typedialog-ai/` +- Vapora: `vapora/crates/vapora-llm-router/` +- Provisioning: `provisioning/platform/crates/rag/` + +**Kogral**: `kogral/` + +**Target Location**: `stratumiops/crates/stratum-llm/` diff --git a/docs/en/architecture/adrs/README.md b/docs/en/architecture/adrs/README.md new file mode 100644 index 0000000..7516fb5 --- /dev/null +++ b/docs/en/architecture/adrs/README.md @@ -0,0 +1,22 @@ +# ADRs - Architecture Decision Records + +Architecture decision records for the STRATUMIOPS ecosystem. + +## Active ADRs + +| ID | Title | Status | +| -------------------------------- | --------------------------------------------- | -------- | +| [001](001-stratum-embeddings.md) | Stratum-Embeddings: Unified Embedding Library | Proposed | +| [002](002-stratum-llm.md) | Stratum-LLM: Unified LLM Provider Library | Proposed | + +## Statuses + +- **Proposed**: Under review, pending implementation +- **Accepted**: Approved and implemented +- **Deprecated**: Replaced by another ADR +- **Superseded**: Obsolete, see replacement ADR + +## Navigation + +- [Back to architecture](../) +- [Spanish version](../../../es/architecture/adrs/) diff --git a/docs/es/README.md b/docs/es/README.md index 2e04001..edd1d3e 100644 --- a/docs/es/README.md +++ b/docs/es/README.md @@ -34,6 +34,16 @@ Herramientas de automatización de infraestructura y despliegue. Ver directorio [ops/](ops/) para documentación completa del portfolio de operaciones. +### Arquitectura + +Decisiones arquitecturales y diseño del ecosistema. + +- [**ADRs**](architecture/adrs/) - Architecture Decision Records + - [ADR-001: Stratum-Embeddings](architecture/adrs/001-stratum-embeddings.md) - Biblioteca unificada de embeddings + - [ADR-002: Stratum-LLM](architecture/adrs/002-stratum-llm.md) - Biblioteca unificada de providers LLM + +Ver directorio [architecture/](architecture/) para documentación completa de arquitectura. + ## Navegación - [Volver a documentación raíz](../) diff --git a/docs/es/architecture/README.md b/docs/es/architecture/README.md new file mode 100644 index 0000000..9857f6d --- /dev/null +++ b/docs/es/architecture/README.md @@ -0,0 +1,30 @@ +# Arquitectura + +Documentación de arquitectura del ecosistema STRATUMIOPS. + +## Contenido + +### ADRs (Architecture Decision Records) + +Decisiones arquitecturales documentadas siguiendo el formato ADR: + +- [**ADR-001: Stratum-Embeddings**](adrs/001-stratum-embeddings.md) - Biblioteca unificada de embeddings +- [**ADR-002: Stratum-LLM**](adrs/002-stratum-llm.md) - Biblioteca unificada de providers LLM + +## Formato ADR + +Cada ADR sigue la estructura: + +| Sección | Descripción | +| ----------------- | ------------------------------------------ | +| Estado | Propuesto, Aceptado, Deprecado, Superseded | +| Contexto | Problema y estado actual | +| Decisión | Solución elegida | +| Justificación | Por qué esta solución | +| Consecuencias | Positivas, negativas, mitigaciones | +| Métricas de Éxito | Cómo medir el resultado | + +## Navegación + +- [Volver a documentación principal](../) +- [English version](../../en/architecture/) diff --git a/docs/es/architecture/adrs/001-stratum-embeddings.md b/docs/es/architecture/adrs/001-stratum-embeddings.md new file mode 100644 index 0000000..514bc5c --- /dev/null +++ b/docs/es/architecture/adrs/001-stratum-embeddings.md @@ -0,0 +1,280 @@ +# ADR-001: Stratum-Embeddings - Biblioteca Unificada de Embeddings + +## Estado + +**Propuesto** + +## Contexto + +### Estado Actual: Implementaciones Fragmentadas + +El ecosistema tiene 3 implementaciones independientes de embeddings: + +| Proyecto | Ubicación | Providers | Caching | +| ------------ | ------------------------------------- | ----------------------------- | ------- | +| Kogral | `kogral-core/src/embeddings/` | fastembed, rig-core (parcial) | No | +| Provisioning | `provisioning-rag/src/embeddings.rs` | OpenAI directo | No | +| Vapora | `vapora-llm-router/src/embeddings.rs` | OpenAI, HuggingFace, Ollama | No | + +### Problemas Identificados + +#### 1. Código Duplicado + +Cada proyecto reimplementa: + +- HTTP client para OpenAI embeddings +- Parsing de respuestas JSON +- Manejo de errores +- Token estimation + +**Impacto**: ~400 líneas duplicadas, inconsistencias en manejo de errores. + +#### 2. Sin Caching + +Embeddings se regeneran cada vez: + +```text +"What is Rust?" → OpenAI → 1536 dims → $0.00002 +"What is Rust?" → OpenAI → 1536 dims → $0.00002 (mismo resultado) +"What is Rust?" → OpenAI → 1536 dims → $0.00002 (mismo resultado) +``` + +**Impacto**: Costos innecesarios, latencia adicional, rate limits más frecuentes. + +#### 3. No Hay Fallback + +Si OpenAI falla, todo falla. No hay fallback a alternativas locales (fastembed, Ollama). + +**Impacto**: Disponibilidad reducida, dependencia total de un provider. + +#### 4. Dimension Mismatch Silencioso + +Diferentes providers producen diferentes dimensiones: + +| Provider | Modelo | Dimensiones | +| --------- | ---------------------- | ----------- | +| fastembed | bge-small-en | 384 | +| fastembed | bge-large-en | 1024 | +| OpenAI | text-embedding-3-small | 1536 | +| OpenAI | text-embedding-3-large | 3072 | +| Ollama | nomic-embed-text | 768 | + +**Impacto**: Índices vectoriales corruptos si se cambia de provider. + +#### 5. Sin Métricas + +No hay visibilidad de uso, hit rate de cache, latencia por provider, ni costos acumulados. + +## Decisión + +Crear `stratum-embeddings` como crate unificado que: + +1. **Unifique** las implementaciones de Kogral, Provisioning, y Vapora +2. **Añada caching** para evitar re-computar embeddings idénticos +3. **Implemente fallback** entre providers (cloud → local) +4. **Documente claramente** las dimensiones y limitaciones por provider +5. **Exponga métricas** para observabilidad +6. **Provea VectorStore trait** con backends LanceDB y SurrealDB según necesidad del proyecto + +### Decisión de Backend de Storage + +Cada proyecto elige su backend de vector storage según su prioridad: + +| Proyecto | Backend | Prioridad | Justificación | +| ------------ | --------- | ----------------- | -------------------------------------------------------- | +| Kogral | SurrealDB | Riqueza del grafo | Knowledge Graph necesita queries unificados graph+vector | +| Provisioning | LanceDB | Escala vectorial | RAG con millones de chunks documentales | +| Vapora | LanceDB | Escala vectorial | Traces de ejecución, pattern matching a escala | + +#### Por qué SurrealDB para Kogral + +Kogral es un Knowledge Graph donde las relaciones son el valor principal. +Con arquitectura híbrida (LanceDB vectores + SurrealDB graph), un query típico requeriría: + +1. LanceDB: búsqueda vectorial → candidate_ids +2. SurrealDB: filtro de grafo sobre candidates → results +3. App layer: merge, re-rank, deduplicación + +**Trade-off aceptado**: SurrealDB tiene peor rendimiento vectorial puro que LanceDB, +pero la escala de Kogral está limitada por curación humana del conocimiento +(10K-100K conceptos típicamente). + +#### Por qué LanceDB para Provisioning y Vapora + +| Aspecto | SurrealDB | LanceDB | +| --------------- | ---------- | -------------------- | +| Storage format | Row-based | Columnar (Lance) | +| Vector index | HNSW (RAM) | IVF-PQ (disk-native) | +| Escala práctica | Millones | Billones | +| Compresión | ~1x | ~32x (PQ) | +| Zero-copy read | No | Sí | + +### Arquitectura + +```text +┌─────────────────────────────────────────────────────────────────┐ +│ stratum-embeddings │ +├─────────────────────────────────────────────────────────────────┤ +│ EmbeddingProvider trait │ +│ ├─ embed(text) → Vec │ +│ ├─ embed_batch(texts) → Vec> │ +│ ├─ dimensions() → usize │ +│ └─ is_local() → bool │ +│ │ +│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ +│ │ FastEmbed │ │ OpenAI │ │ Ollama │ │ +│ │ (local) │ │ (cloud) │ │ (local) │ │ +│ └───────────┘ └───────────┘ └───────────┘ │ +│ └────────────┬────────────┘ │ +│ ▼ │ +│ EmbeddingCache (memory/disk) │ +│ │ │ +│ ▼ │ +│ EmbeddingService │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ VectorStore trait │ │ +│ │ ├─ upsert(id, embedding, metadata) │ │ +│ │ ├─ search(embedding, limit, filter) → Vec │ │ +│ │ └─ delete(id) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ SurrealDbStore │ │ LanceDbStore │ │ +│ │ (Kogral) │ │ (Prov/Vapora) │ │ +│ └─────────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Justificación + +### Por Qué Caching es Crítico + +Para un sistema RAG típico (10,000 chunks): + +- **Sin cache**: Re-indexaciones y queries repetidas multiplican costos +- **Con cache**: Primera indexación paga, resto son cache hits + +**Ahorro estimado**: 60-80% en costos de embeddings. + +### Por Qué Fallback es Importante + +| Escenario | Sin Fallback | Con Fallback | +| ----------------- | ------------ | ------------------- | +| OpenAI rate limit | ERROR | → fastembed (local) | +| OpenAI downtime | ERROR | → Ollama (local) | +| Sin internet | ERROR | → fastembed (local) | + +### Por Qué Providers Locales Primero + +Para desarrollo: fastembed carga modelo local (~100MB), no requiere API keys, sin costos, funciona offline. + +Para producción: OpenAI para calidad, fastembed como fallback. + +## Consecuencias + +### Positivas + +1. Single source of truth para todo el ecosistema +2. 60-80% menos llamadas a APIs de embeddings (caching) +3. Alta disponibilidad con providers locales (fallback) +4. Métricas de uso y costos +5. Feature-gated: solo compila lo necesario +6. Storage flexibility: VectorStore trait permite elegir backend por proyecto + +### Negativas + +1. **Dimension lock-in**: Cambiar provider requiere re-indexar +2. **Cache invalidation**: Contenido actualizado puede servir embeddings stale +3. **Model download**: fastembed descarga ~100MB en primer uso +4. **Storage lock-in por proyecto**: Kogral atado a SurrealDB, otros a LanceDB + +### Mitigaciones + +| Negativo | Mitigación | +| ----------------- | ------------------------------------------------------ | +| Dimension lock-in | Documentar claramente, warn en cambio de provider | +| Cache stale | TTL configurable, opción de bypass | +| Model download | Mostrar progreso, cache en ~/.cache/fastembed | +| Storage lock-in | Decisión consciente basada en prioridades del proyecto | + +## Métricas de Éxito + +| Métrica | Actual | Objetivo | +| --------------------------- | ------ | -------- | +| Implementaciones duplicadas | 3 | 1 | +| Cache hit rate | 0% | >60% | +| Fallback availability | 0% | 100% | +| Cost per 10K embeddings | ~$0.20 | ~$0.05 | + +## Guía de Selección de Provider + +### Desarrollo + +```rust +// Local, gratis, offline +let service = EmbeddingService::builder() + .with_provider(FastEmbedProvider::small()?) // 384 dims + .with_memory_cache() + .build()?; +``` + +### Producción (Calidad) + +```rust +// OpenAI con fallback local +let service = EmbeddingService::builder() + .with_provider(OpenAiEmbeddingProvider::large()?) // 3072 dims + .with_provider(FastEmbedProvider::large()?) // Fallback + .with_memory_cache() + .build()?; +``` + +### Producción (Costo-Optimizado) + +```rust +// OpenAI small con fallback +let service = EmbeddingService::builder() + .with_provider(OpenAiEmbeddingProvider::small()?) // 1536 dims + .with_provider(OllamaEmbeddingProvider::nomic()) // Fallback + .with_memory_cache() + .build()?; +``` + +## Matriz de Compatibilidad de Dimensiones + +| Si usas... | Puedes cambiar a... | NO puedes cambiar a... | +| ---------------------- | --------------------------- | ---------------------- | +| fastembed small (384) | fastembed small, all-minilm | Cualquier otro | +| fastembed large (1024) | fastembed large | Cualquier otro | +| OpenAI small (1536) | OpenAI small, ada-002 | Cualquier otro | +| OpenAI large (3072) | OpenAI large | Cualquier otro | + +**Regla**: Solo puedes cambiar entre modelos con las MISMAS dimensiones. + +## Prioridad de Implementación + +| Orden | Feature | Razón | +| ----- | ----------------------- | ---------------------------- | +| 1 | EmbeddingProvider trait | Base para todo | +| 2 | FastEmbed provider | Funciona sin API keys | +| 3 | Memory cache | Mayor impacto en costos | +| 4 | VectorStore trait | Abstracción de storage | +| 5 | SurrealDbStore | Kogral necesita graph+vector | +| 6 | LanceDbStore | Provisioning/Vapora escala | +| 7 | OpenAI provider | Producción | +| 8 | Ollama provider | Fallback local | +| 9 | Batch processing | Eficiencia | +| 10 | Metrics | Observabilidad | + +## Referencias + +**Implementaciones Existentes**: + +- Kogral: `kogral-core/src/embeddings/` +- Vapora: `vapora-llm-router/src/embeddings.rs` +- Provisioning: `provisioning/platform/crates/rag/src/embeddings.rs` + +**Ubicación Objetivo**: `stratumiops/crates/stratum-embeddings/` diff --git a/docs/es/architecture/adrs/002-stratum-llm.md b/docs/es/architecture/adrs/002-stratum-llm.md new file mode 100644 index 0000000..a853be9 --- /dev/null +++ b/docs/es/architecture/adrs/002-stratum-llm.md @@ -0,0 +1,279 @@ +# ADR-002: Stratum-LLM - Biblioteca Unificada de Providers LLM + +## Estado + +**Propuesto** + +## Contexto + +### Estado Actual: Conexiones LLM Fragmentadas + +El ecosistema stratumiops tiene 4 proyectos con funcionalidad IA, cada uno con su propia implementación: + +| Proyecto | Implementación | Providers | Duplicación | +| ------------ | -------------------------- | ---------------------- | --------------------- | +| Vapora | `typedialog-ai` (path dep) | Claude, OpenAI, Ollama | Base compartida | +| TypeDialog | `typedialog-ai` (local) | Claude, OpenAI, Ollama | Define la abstracción | +| Provisioning | Custom `LlmClient` | Claude, OpenAI | 100% duplicado | +| Kogral | `rig-core` | Solo embeddings | Diferente stack | + +### Problemas Identificados + +#### 1. Duplicación de Código + +Provisioning reimplementa lo que TypeDialog ya tiene: + +- reqwest HTTP client +- Headers: x-api-key, anthropic-version +- JSON body formatting +- Response parsing +- Error handling + +**Impacto**: ~500 líneas duplicadas, bugs arreglados en un lugar no se propagan. + +#### 2. Solo API Keys, No CLI Detection + +Ningún proyecto detecta credenciales de CLIs oficiales: + +```text +Claude CLI: ~/.config/claude/credentials.json +OpenAI CLI: ~/.config/openai/credentials.json +``` + +**Impacto**: Usuarios con Claude Pro/Max ($20-100/mes) pagan API tokens cuando podrían usar su suscripción. + +#### 3. Sin Fallback Automático + +Cuando un provider falla (rate limit, timeout), la request falla completamente: + +```text +Actual: Request → Claude API → Rate Limit → ERROR +Deseado: Request → Claude API → Rate Limit → OpenAI → Success +``` + +#### 4. Sin Circuit Breaker + +Si Claude API está caído, cada request intenta conectar, falla, y propaga el error: + +```text +Request 1 → Claude → Timeout (30s) → Error +Request 2 → Claude → Timeout (30s) → Error +Request 3 → Claude → Timeout (30s) → Error +``` + +**Impacto**: Latencia acumulada, UX degradado. + +#### 5. Sin Caching + +Requests idénticas van siempre a la API: + +```text +"Explain this Rust error" → Claude → $0.003 +"Explain this Rust error" → Claude → $0.003 (mismo resultado) +``` + +**Impacto**: Costos innecesarios, especialmente en desarrollo/testing. + +#### 6. Kogral No Integrado + +Kogral tiene guidelines y patterns que podrían enriquecer el contexto de LLM, pero no hay integración. + +## Decisión + +Crear `stratum-llm` como crate unificado que: + +1. **Consolide** las implementaciones existentes de typedialog-ai y provisioning +2. **Detecte** credenciales CLI y subscripciones antes de usar API keys +3. **Implemente** fallback automático con circuit breaker +4. **Añada** caching de requests para reducir costos +5. **Integre** Kogral para enriquecer contexto +6. **Sea usado** por todos los proyectos del ecosistema + +### Arquitectura + +```text +┌─────────────────────────────────────────────────────────┐ +│ stratum-llm │ +├─────────────────────────────────────────────────────────┤ +│ CredentialDetector │ +│ ├─ Claude CLI → ~/.config/claude/ (subscription) │ +│ ├─ OpenAI CLI → ~/.config/openai/ │ +│ ├─ Env vars → *_API_KEY │ +│ └─ Ollama → localhost:11434 (free) │ +│ │ │ +│ ▼ │ +│ ProviderChain (ordered by priority) │ +│ [CLI/Sub] → [API] → [DeepSeek] → [Ollama] │ +│ │ │ │ │ │ +│ └──────────┴─────────┴───────────┘ │ +│ │ │ +│ CircuitBreaker per provider │ +│ │ │ +│ RequestCache │ +│ │ │ +│ KogralIntegration │ +│ │ │ +│ UnifiedClient │ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +## Justificación + +### Por Qué No Usar Otra Crate Externa + +| Alternativa | Por Qué No | +| -------------- | ------------------------------------------ | +| kaccy-ai | Orientada a blockchain/fraud detection | +| llm (crate) | Muy básica, sin circuit breaker ni caching | +| langchain-rust | Port de Python, no idiomático Rust | +| rig-core | Solo embeddings/RAG, no chat completion | + +**Mejor opción**: Construir sobre typedialog-ai y añadir features faltantes. + +### Por Qué CLI Detection es Importante + +Análisis de costos para usuario típico: + +| Escenario | Costo Mensual | +| ------------------------- | -------------------- | +| Solo API (actual) | ~$840 | +| Claude Pro + API overflow | ~$20 + ~$200 = $220 | +| Claude Max + API overflow | ~$100 + ~$50 = $150 | + +**Ahorro potencial**: 70-80% detectando y usando subscripciones primero. + +### Por Qué Circuit Breaker + +Sin circuit breaker, un provider caído causa: + +- N requests × 30s timeout = N×30s de latencia total +- Todos los recursos ocupados esperando timeouts + +Con circuit breaker: + +- Primera falla abre circuito +- Siguientes requests fallan inmediatamente (fast fail) +- Fallback a otro provider sin esperar +- Circuito se resetea después de cooldown + +### Por Qué Caching + +Para desarrollo típico: + +- Mismas preguntas repetidas mientras se itera +- Testing ejecuta mismos prompts múltiples veces + +Cache hit rate estimado: 15-30% en desarrollo activo. + +### Por Qué Kogral Integration + +Kogral tiene guidelines por lenguaje, patterns por dominio, y ADRs. +Sin integración el LLM genera código genérico; +con integración genera código que sigue convenciones del proyecto. + +## Consecuencias + +### Positivas + +1. Single source of truth para lógica de LLM +2. CLI detection reduce costos 70-80% +3. Circuit breaker + fallback = alta disponibilidad +4. 15-30% menos requests en desarrollo (caching) +5. Kogral mejora calidad de generación +6. Feature-gated: cada feature es opcional + +### Negativas + +1. **Migration effort**: Refactorizar Vapora, TypeDialog, Provisioning +2. **New dependency**: Proyectos dependen de stratumiops +3. **CLI auth complexity**: Diferentes formatos de credenciales por versión +4. **Cache invalidation**: Respuestas obsoletas si no se gestiona bien + +### Mitigaciones + +| Negativo | Mitigación | +| ------------------- | -------------------------------------------- | +| Migration effort | Re-export API compatible desde typedialog-ai | +| New dependency | Path dependency local, no crates.io | +| CLI auth complexity | Version detection, fallback a API si falla | +| Cache invalidation | TTL configurable, opción de bypass | + +## Métricas de Éxito + +| Métrica | Actual | Objetivo | +| --------------------------- | ------ | --------------- | +| Líneas de código duplicadas | ~500 | 0 | +| CLI credential detection | 0% | 100% | +| Fallback success rate | 0% | >90% | +| Cache hit rate | 0% | 15-30% | +| Latency (provider down) | 30s+ | <1s (fast fail) | + +## Análisis de Impacto en Costos + +Basado en datos reales de uso ($840/mes): + +| Escenario | Ahorro | +| ----------------------------- | ---------------- | +| CLI detection (Claude Max) | ~$700/mes | +| Caching (15% hit rate) | ~$50/mes | +| DeepSeek fallback para código | ~$100/mes | +| **Total potencial** | **$500-700/mes** | + +## Estrategia de Migración + +### Fases de Migración + +1. Crear stratum-llm con API compatible con typedialog-ai +2. typedialog-ai re-exporta stratum-llm (backward compatible) +3. Vapora migra a stratum-llm directamente +4. Provisioning migra su LlmClient a stratum-llm +5. Deprecar typedialog-ai, consolidar en stratum-llm + +### Adopción de Features + +| Feature | Adopción | +| --------------- | --------------------------------------- | +| Basic providers | Inmediata (reemplazo directo) | +| CLI detection | Opcional, feature flag | +| Circuit breaker | Default on | +| Caching | Default on, configurable TTL | +| Kogral | Feature flag, requiere Kogral instalado | + +## Alternativas Consideradas + +### Alternativa 1: Mejorar typedialog-ai In-Place + +**Pros**: No requiere nuevo crate + +**Cons**: TypeDialog es proyecto específico, no infraestructura compartida + +**Decisión**: stratum-llm en stratumiops es mejor ubicación para infraestructura cross-project. + +### Alternativa 2: Usar LiteLLM (Python) como Proxy + +**Pros**: Muy completo, 100+ providers + +**Cons**: Dependencia Python, latencia de proxy, no Rust-native + +**Decisión**: Mantener stack Rust puro. + +### Alternativa 3: Cada Proyecto Mantiene su Implementación + +**Pros**: Independencia + +**Cons**: Duplicación, inconsistencia, bugs no compartidos + +**Decisión**: Consolidar es mejor a largo plazo. + +## Referencias + +**Implementaciones Existentes**: + +- TypeDialog: `typedialog/crates/typedialog-ai/` +- Vapora: `vapora/crates/vapora-llm-router/` +- Provisioning: `provisioning/platform/crates/rag/` + +**Kogral**: `kogral/` + +**Ubicación Objetivo**: `stratumiops/crates/stratum-llm/` diff --git a/docs/es/architecture/adrs/README.md b/docs/es/architecture/adrs/README.md new file mode 100644 index 0000000..6cef791 --- /dev/null +++ b/docs/es/architecture/adrs/README.md @@ -0,0 +1,22 @@ +# ADRs - Architecture Decision Records + +Registro de decisiones arquitecturales del ecosistema STRATUMIOPS. + +## ADRs Activos + +| ID | Título | Estado | +| -------------------------------- | ------------------------------------------------------ | --------- | +| [001](001-stratum-embeddings.md) | Stratum-Embeddings: Biblioteca Unificada de Embeddings | Propuesto | +| [002](002-stratum-llm.md) | Stratum-LLM: Biblioteca Unificada de Providers LLM | Propuesto | + +## Estados + +- **Propuesto**: En revisión, pendiente de implementación +- **Aceptado**: Aprobado e implementado +- **Deprecado**: Reemplazado por otro ADR +- **Superseded**: Obsoleto, ver ADR de reemplazo + +## Navegación + +- [Volver a arquitectura](../) +- [English version](../../../en/architecture/adrs/)