From 6335aba33ba3e87045be422849406c287e431948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Tue, 3 Feb 2026 22:29:15 +0000 Subject: [PATCH] chore: update crates --- Cargo.lock | 263 +++++++++++++++--- Cargo.toml | 8 +- crates/kogral-cli/src/main.rs | 24 +- crates/kogral-core/Cargo.toml | 7 +- crates/kogral-core/src/block_parser.rs | 8 +- .../kogral-core/src/embeddings/fastembed.rs | 113 -------- crates/kogral-core/src/embeddings/mod.rs | 26 +- crates/kogral-core/src/embeddings/rig.rs | 109 -------- crates/kogral-core/src/parser.rs | 2 +- crates/kogral-core/src/regex_patterns.rs | 8 +- .../tests/nickel_integration_test.rs | 2 +- crates/kogral-mcp/src/auth.rs | 3 +- crates/kogral-mcp/src/server.rs | 11 +- crates/kogral-mcp/src/tools.rs | 56 +++- crates/kogral-mcp/src/validation.rs | 14 +- 15 files changed, 335 insertions(+), 319 deletions(-) delete mode 100644 crates/kogral-core/src/embeddings/fastembed.rs delete mode 100644 crates/kogral-core/src/embeddings/rig.rs diff --git a/Cargo.lock b/Cargo.lock index aa95ffc..70eeee7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[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" @@ -332,7 +344,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "static_assertions_next", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -349,7 +361,7 @@ dependencies = [ "quote", "strum", "syn 2.0.114", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -394,6 +406,17 @@ dependencies = [ "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-stream" version = "0.3.6" @@ -490,7 +513,7 @@ dependencies = [ "num-traits", "pastey", "rayon", - "thiserror 2.0.17", + "thiserror 2.0.18", "v_frame", "y4m", ] @@ -1070,6 +1093,15 @@ 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" @@ -1411,6 +1443,12 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "double-ended-peekable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57" + [[package]] name = "dtoa" version = "1.0.11" @@ -1567,6 +1605,19 @@ dependencies = [ "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 = "fastembed" version = "5.8.1" @@ -2055,6 +2106,10 @@ 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" @@ -2149,7 +2204,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "ureq 2.12.1", "windows-sys 0.60.2", ] @@ -2240,6 +2295,16 @@ 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 = "1.8.1" @@ -2700,7 +2765,6 @@ dependencies = [ "async-trait", "chrono", "dashmap 6.1.0", - "fastembed", "futures", "mockito", "notify", @@ -2711,10 +2775,11 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "stratum-embeddings", "surrealdb", "tempfile", "tera", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "toml", "tracing", @@ -2889,6 +2954,15 @@ 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" @@ -3088,6 +3162,26 @@ dependencies = [ "tokio", ] +[[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", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + [[package]] name = "monostate" version = "0.1.18" @@ -3404,7 +3498,7 @@ dependencies = [ "itertools 0.14.0", "parking_lot", "percent-encoding", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -4001,6 +4095,18 @@ dependencies = [ "parking_lot", ] +[[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", +] + [[package]] name = "quinn" version = "0.11.9" @@ -4015,7 +4121,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -4036,7 +4142,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -4177,7 +4283,7 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "simd_helpers", - "thiserror 2.0.17", + "thiserror 2.0.18", "v_frame", "wasm-bindgen", ] @@ -4268,7 +4374,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4377,6 +4483,15 @@ dependencies = [ "webpki-roots 1.0.5", ] +[[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" @@ -4386,12 +4501,23 @@ dependencies = [ "chrono", "geo", "regex", - "revision-derive", + "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" @@ -4411,9 +4537,9 @@ checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" [[package]] name = "rig-core" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1a48121c1ecd6f6ce59d64ec353c791aac6fc07bf4aa353380e8185659e6eb" +checksum = "a8f7a3f0c7c00eaced15a68ee16e1bd6bb709ff598d11b9aedac8b628217dc09" dependencies = [ "as-any", "async-stream", @@ -4427,13 +4553,14 @@ dependencies = [ "http", "mime", "mime_guess", + "nanoid", "ordered-float", "pin-project-lite", "reqwest", "schemars 1.2.0", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "tracing-futures", @@ -4492,6 +4619,16 @@ 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" @@ -5039,7 +5176,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", ] @@ -5183,6 +5320,24 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "stratum-embeddings" +version = "0.1.0" +dependencies = [ + "async-trait", + "fastembed", + "futures", + "humantime-serde", + "moka", + "serde", + "serde_json", + "surrealdb", + "thiserror 2.0.18", + "tokio", + "tracing", + "xxhash-rust", +] + [[package]] name = "string_cache" version = "0.8.9" @@ -5243,9 +5398,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "surrealdb" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f921fcafdc840d36a4378ef7639fcb2731a21a858b048de83f0bd7194c242479" +checksum = "62b7720b39ce2985efbfa10858b7397ffd95655a9bab6d9dfaa03622bbdc3bc2" dependencies = [ "arrayvec", "async-channel", @@ -5260,7 +5415,7 @@ dependencies = [ "pharos", "reblessive", "reqwest", - "revision", + "revision 0.11.0", "ring", "rust_decimal", "rustls", @@ -5285,11 +5440,12 @@ dependencies = [ [[package]] name = "surrealdb-core" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae1a46c6d68a61c0a270f456a152433093f4d5c0e71c45eea64f95e95d68bd9" +checksum = "c48e42c81713be2f9b3dae64328999eafe8b8060dd584059445a908748b39787" dependencies = [ "addr", + "affinitypool", "ahash 0.8.12", "ammonia", "any_ascii", @@ -5309,12 +5465,14 @@ dependencies = [ "dashmap 5.5.3", "deunicode", "dmp", + "ext-sort", "fst", "futures", "fuzzy-matcher", "geo", "geo-types", "getrandom 0.3.4", + "hashbrown 0.14.5", "hex", "http", "ipnet", @@ -5333,13 +5491,13 @@ dependencies = [ "pharos", "phf", "pin-project-lite", - "quick_cache", + "quick_cache 0.5.2", "radix_trie", "rand 0.8.5", "rayon", "reblessive", "regex", - "revision", + "revision 0.11.0", "ring", "rmpv", "roaring", @@ -5356,7 +5514,9 @@ dependencies = [ "storekey", "strsim", "subtle", + "surrealkv", "sysinfo", + "tempfile", "thiserror 1.0.69", "tokio", "tracing", @@ -5365,12 +5525,31 @@ dependencies = [ "unicase", "url", "uuid", - "vart", + "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", + "quick_cache 0.6.18", + "revision 0.10.0", + "vart 0.9.3", +] + [[package]] name = "syn" version = "1.0.109" @@ -5448,6 +5627,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -5522,11 +5707,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -5542,9 +5727,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -5666,7 +5851,7 @@ dependencies = [ "serde", "serde_json", "spm_precompiled", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-normalization-alignments", "unicode-segmentation", "unicode_categories", @@ -6156,9 +6341,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -6189,6 +6374,12 @@ 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" @@ -6738,7 +6929,7 @@ dependencies = [ "pharos", "rustc_version", "send_wrapper", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6753,6 +6944,12 @@ dependencies = [ "tap", ] +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + [[package]] name = "y4m" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 628da29..c66e4ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ toml = "0.9" tokio = { version = "1.49", features = ["full"] } # Error handling -thiserror = "2.0" +thiserror = "2.0.18" anyhow = "1.0" # Markdown parsing @@ -38,10 +38,10 @@ pulldown-cmark = "0.13" tera = "1.20" # Embeddings -rig-core = "0.28" +rig-core = "0.30" # Storage -surrealdb = "2.4" +surrealdb = "2.6" dashmap = "6.1" # File watching @@ -51,7 +51,7 @@ notify = "8.2" chrono = { version = "0.4", features = ["serde"] } # UUID generation -uuid = { version = "1.19", features = ["v4", "serde"] } +uuid = { version = "1.20", features = ["v4", "serde"] } # Logging tracing = "0.1" diff --git a/crates/kogral-cli/src/main.rs b/crates/kogral-cli/src/main.rs index d927ce3..dfe833e 100644 --- a/crates/kogral-cli/src/main.rs +++ b/crates/kogral-cli/src/main.rs @@ -508,7 +508,7 @@ watch_paths = ["notes", "decisions", "guidelines", "patterns", "journal"] /// Add a new node async fn cmd_add( - project_dir: &PathBuf, + project_dir: &Path, config: Option, node_type: CliNodeType, title: String, @@ -566,7 +566,7 @@ async fn cmd_add( /// Search KOGRAL async fn cmd_search( - project_dir: &PathBuf, + project_dir: &Path, config: Option, query: String, node_type: Option, @@ -652,7 +652,7 @@ async fn cmd_search( /// Create a relationship between nodes async fn cmd_link( - project_dir: &PathBuf, + project_dir: &Path, config: Option, from: String, to: String, @@ -710,7 +710,7 @@ async fn cmd_link( /// Sync filesystem with storage async fn cmd_sync( - _project_dir: &PathBuf, + _project_dir: &Path, config: Option, direction: String, dry_run: bool, @@ -744,7 +744,7 @@ async fn cmd_sync( /// Start MCP server async fn cmd_serve( - _project_dir: &PathBuf, + _project_dir: &Path, _config: Option, transport: String, port: u16, @@ -784,7 +784,7 @@ async fn cmd_serve( /// Show graph visualization #[allow(clippy::excessive_nesting)] async fn cmd_graph( - project_dir: &PathBuf, + project_dir: &Path, config: Option, format: String, node_type: Option, @@ -873,7 +873,7 @@ async fn cmd_graph( /// Import from external sources async fn cmd_import( - _project_dir: &PathBuf, + _project_dir: &Path, config: Option, source: ImportSource, path: PathBuf, @@ -948,7 +948,7 @@ async fn cmd_import( /// Export to external formats async fn cmd_export( - project_dir: &PathBuf, + project_dir: &Path, config: Option, format: ExportFormat, output: PathBuf, @@ -1066,7 +1066,7 @@ async fn cmd_export( /// List all nodes async fn cmd_list( - project_dir: &PathBuf, + project_dir: &Path, config: Option, node_type: Option, status: Option, @@ -1134,7 +1134,7 @@ async fn cmd_list( /// Show node details #[allow(clippy::excessive_nesting)] async fn cmd_show( - project_dir: &PathBuf, + project_dir: &Path, config: Option, node: String, relations: bool, @@ -1199,7 +1199,7 @@ async fn cmd_show( /// Delete a node async fn cmd_delete( - project_dir: &PathBuf, + project_dir: &Path, config: Option, node: String, cascade: bool, @@ -1249,7 +1249,7 @@ async fn cmd_delete( /// Manage configuration async fn cmd_config( - _project_dir: &PathBuf, + _project_dir: &Path, config: Option, show: bool, get: Option, diff --git a/crates/kogral-core/Cargo.toml b/crates/kogral-core/Cargo.toml index 0604be8..6cbee89 100644 --- a/crates/kogral-core/Cargo.toml +++ b/crates/kogral-core/Cargo.toml @@ -30,12 +30,14 @@ tracing = { workspace = true } # Additional dependencies async-trait = { workspace = true } -fastembed = { workspace = true, optional = true } regex = { workspace = true } once_cell = { workspace = true } walkdir = { workspace = true } futures = { workspace = true } +# Stratum embeddings +stratum-embeddings = { path = "/Users/Akasha/Development/stratumiops/crates/stratum-embeddings", features = ["kogral"] } + [dev-dependencies] tokio = { workspace = true, features = ["test-util", "macros"] } mockito = { workspace = true } @@ -45,5 +47,4 @@ tempfile = { workspace = true } default = ["filesystem"] filesystem = [] surrealdb-backend = ["surrealdb"] -fastembed = ["dep:fastembed"] -full = ["surrealdb-backend", "fastembed"] +full = ["surrealdb-backend"] diff --git a/crates/kogral-core/src/block_parser.rs b/crates/kogral-core/src/block_parser.rs index c5b41e5..7e52e8c 100644 --- a/crates/kogral-core/src/block_parser.rs +++ b/crates/kogral-core/src/block_parser.rs @@ -15,8 +15,8 @@ use std::fmt::Write; use crate::error::Result; use crate::models::{Block, TaskStatus}; use crate::regex_patterns::{ - PROPERTY_START_PATTERN, TAG_PATTERN, PROPERTY_INLINE_PATTERN, UUID_REF_PATTERN, - LOGSEQ_WIKILINK_PATTERN, + LOGSEQ_WIKILINK_PATTERN, PROPERTY_INLINE_PATTERN, PROPERTY_START_PATTERN, TAG_PATTERN, + UUID_REF_PATTERN, }; /// Parser for converting markdown outliner format to/from Block structures @@ -208,7 +208,9 @@ impl BlockParser { } // Remove properties from content - clean_content = PROPERTY_INLINE_PATTERN.replace_all(&clean_content, "").to_string(); + clean_content = PROPERTY_INLINE_PATTERN + .replace_all(&clean_content, "") + .to_string(); (properties, clean_content) } diff --git a/crates/kogral-core/src/embeddings/fastembed.rs b/crates/kogral-core/src/embeddings/fastembed.rs deleted file mode 100644 index 311e602..0000000 --- a/crates/kogral-core/src/embeddings/fastembed.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! `FastEmbed` local embedding provider - -use crate::embeddings::EmbeddingProvider; -use crate::error::Result; - -/// Local embedding provider using `FastEmbed` -/// -/// Provides embedding generation without external API calls. -/// Uses local models for privacy and offline capability. -/// -/// Default model: BAAI/bge-small-en-v1.5 (384 dimensions) -/// Supports CPU inference with minimal memory footprint. -#[cfg(feature = "fastembed")] -pub struct FastEmbedProvider { - model: fastembed::FlagEmbedding, - dimensions: usize, -} - -#[cfg(feature = "fastembed")] -impl FastEmbedProvider { - /// Create a new `FastEmbed` provider with default settings - /// - /// Uses BAAI/bge-small-en-v1.5 model by default (384 dimensions). - /// - /// # Errors - /// - /// Returns error if model initialization fails (e.g., download issues). - pub fn new() -> Result { - let model = fastembed::FlagEmbedding::try_new(Default::default()).map_err(|e| { - crate::error::KbError::Embedding(format!("Failed to initialize FastEmbed: {}", e)) - })?; - - Ok(Self { - dimensions: 384, // BAAI/bge-small-en-v1.5 dimensions - model, - }) - } - - /// Create with custom dimensions (for compatibility) - /// - /// Note: Actual dimensions will be determined by the model, - /// this parameter is for API compatibility. - /// - /// # Errors - /// - /// Returns error if model initialization fails. - pub fn with_dimensions(dimensions: usize) -> Result { - let model = fastembed::FlagEmbedding::try_new(Default::default()).map_err(|e| { - crate::error::KbError::Embedding(format!("Failed to initialize FastEmbed: {}", e)) - })?; - - Ok(Self { model, dimensions }) - } -} - -#[cfg(feature = "fastembed")] -impl EmbeddingProvider for FastEmbedProvider { - fn embed(&self, text: &str) -> Result> { - let embeddings = self - .model - .embed(vec![text], None) - .map_err(|e| crate::error::KbError::Embedding(format!("Embedding error: {}", e)))?; - - Ok(embeddings - .into_iter() - .next() - .ok_or_else(|| crate::error::KbError::Embedding("No embedding returned".to_string()))?) - } - - fn dimensions(&self) -> usize { - self.dimensions - } -} - -/// Fallback for when feature is not enabled -#[cfg(not(feature = "fastembed"))] -pub struct FastEmbedProvider { - dimensions: usize, -} - -#[cfg(not(feature = "fastembed"))] -impl FastEmbedProvider { - /// Create a new `FastEmbed` provider (stub when feature not enabled) - /// - /// # Errors - /// - /// Always returns error since fastembed feature is not enabled - pub fn new() -> Result { - Err(crate::error::KbError::Embedding( - "fastembed feature not enabled. Enable with: cargo build --features fastembed" - .to_string(), - )) - } - - /// Create with custom dimensions (stub when feature not enabled) - #[must_use] - pub fn with_dimensions(dimensions: usize) -> Self { - Self { dimensions } - } -} - -#[cfg(not(feature = "fastembed"))] -impl EmbeddingProvider for FastEmbedProvider { - fn embed(&self, _text: &str) -> Result> { - Err(crate::error::KbError::Embedding( - "fastembed feature not enabled".to_string(), - )) - } - - fn dimensions(&self) -> usize { - self.dimensions - } -} diff --git a/crates/kogral-core/src/embeddings/mod.rs b/crates/kogral-core/src/embeddings/mod.rs index 1b0e538..6c872a0 100644 --- a/crates/kogral-core/src/embeddings/mod.rs +++ b/crates/kogral-core/src/embeddings/mod.rs @@ -1,20 +1,10 @@ -//! Embedding generation for semantic search +//! Embedding generation for semantic search using stratum-embeddings -use crate::error::Result; +pub use stratum_embeddings::{ + cosine_similarity, euclidean_distance, normalize_embedding, Embedding, EmbeddingError, + EmbeddingOptions, EmbeddingProvider, EmbeddingResult, FastEmbedProvider, MemoryCache, + ProviderInfo, +}; -/// Embedding provider trait -pub trait EmbeddingProvider: Send + Sync { - /// Generate embeddings for text - /// - /// # Errors - /// - /// Returns an error if embedding generation fails - fn embed(&self, text: &str) -> Result>; - - /// Get embedding dimensions - fn dimensions(&self) -> usize; -} - -// Module stubs -pub mod fastembed; -pub mod rig; +/// Re-export for backward compatibility +pub type Result = std::result::Result; diff --git a/crates/kogral-core/src/embeddings/rig.rs b/crates/kogral-core/src/embeddings/rig.rs deleted file mode 100644 index 42cae84..0000000 --- a/crates/kogral-core/src/embeddings/rig.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! rig-core embedding provider integration - -use crate::embeddings::EmbeddingProvider; -use crate::error::Result; - -/// Embedding provider using rig-core -/// -/// Integrates with cloud embedding APIs (`OpenAI`, Claude, Ollama, etc.) -/// via the rig-core library for production semantic search. -/// -/// Supports multiple embedding services via rig-core abstraction. -/// API key should be set in environment variables (e.g., `OPENAI_API_KEY`). -pub struct RigEmbeddingProvider { - #[allow(dead_code)] - model: String, - dimensions: usize, -} - -impl RigEmbeddingProvider { - /// Create a new rig-core embedding provider - /// - /// # Arguments - /// - /// * `model` - Model identifier (e.g., "text-embedding-3-small", - /// "openai/text-embedding-3-small") - /// * `dimensions` - Expected embedding dimensions - /// - /// # Errors - /// - /// Returns error if required API keys are not configured. - #[must_use] - pub fn new(model: String, dimensions: usize) -> Self { - Self { model, dimensions } - } - - /// Create provider for `OpenAI`'s `text-embedding-3-small` - /// - /// Requires `OPENAI_API_KEY` environment variable. - #[must_use] - pub fn openai_small() -> Self { - Self { - model: "text-embedding-3-small".to_string(), - dimensions: 1536, - } - } - - /// Create provider for `OpenAI`'s `text-embedding-3-large` - /// - /// Requires `OPENAI_API_KEY` environment variable. - #[must_use] - pub fn openai_large() -> Self { - Self { - model: "text-embedding-3-large".to_string(), - dimensions: 3072, - } - } - - /// Create provider for Ollama embeddings (local inference) - /// - /// Requires Ollama running locally on default port 11434. - #[must_use] - pub fn ollama(model: String, dimensions: usize) -> Self { - Self { model, dimensions } - } -} - -impl EmbeddingProvider for RigEmbeddingProvider { - fn embed(&self, _text: &str) -> Result> { - // Note: Full rig-core integration requires async context. - // For now, return error with helpful message about API keys. - // In production, this would use rig-core's embedding client. - - // Check for API key availability - let has_openai_key = std::env::var("OPENAI_API_KEY").is_ok(); - let has_anthropic_key = std::env::var("ANTHROPIC_API_KEY").is_ok(); - - if !has_openai_key && !has_anthropic_key { - return Err(crate::error::KbError::Embedding( - "No embedding service configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY" - .to_string(), - )); - } - - // Return placeholder embeddings with correct dimensions - // In actual implementation, this would call rig-core's embedding service - Ok(vec![0.0; self.dimensions]) - } - - fn dimensions(&self) -> usize { - self.dimensions - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_openai_small_dimensions() { - let provider = RigEmbeddingProvider::openai_small(); - assert_eq!(provider.dimensions(), 1536); - } - - #[test] - fn test_openai_large_dimensions() { - let provider = RigEmbeddingProvider::openai_large(); - assert_eq!(provider.dimensions(), 3072); - } -} diff --git a/crates/kogral-core/src/parser.rs b/crates/kogral-core/src/parser.rs index 2a64dad..b1a78f6 100644 --- a/crates/kogral-core/src/parser.rs +++ b/crates/kogral-core/src/parser.rs @@ -14,7 +14,7 @@ use serde_yaml; use crate::error::{KbError, Result}; use crate::models::{Node, NodeStatus, NodeType}; -use crate::regex_patterns::{WIKILINK_PATTERN, CODE_REF_PATTERN}; +use crate::regex_patterns::{CODE_REF_PATTERN, WIKILINK_PATTERN}; /// Extract frontmatter as a map of key-value strings /// diff --git a/crates/kogral-core/src/regex_patterns.rs b/crates/kogral-core/src/regex_patterns.rs index 60c8b21..081072a 100644 --- a/crates/kogral-core/src/regex_patterns.rs +++ b/crates/kogral-core/src/regex_patterns.rs @@ -1,7 +1,8 @@ //! Lazy-compiled regex patterns for the KOGRAL knowledge base engine //! -//! All regex patterns are compiled once at startup using `once_cell::sync::Lazy` -//! to avoid recompilation overhead during normal operation. +//! All regex patterns are compiled once at startup using +//! `once_cell::sync::Lazy` to avoid recompilation overhead during normal +//! operation. use once_cell::sync::Lazy; use regex::Regex; @@ -11,8 +12,7 @@ pub static WIKILINK_PATTERN: Lazy = Lazy::new(|| Regex::new(r"\[\[([^\]|]+)(?:\|[^\]]+)?\]\]").unwrap()); /// Code reference pattern: `@path/to/file.rs:42` -pub static CODE_REF_PATTERN: Lazy = - Lazy::new(|| Regex::new(r"@([\w/.-]+:\d+)").unwrap()); +pub static CODE_REF_PATTERN: Lazy = Lazy::new(|| Regex::new(r"@([\w/.-]+:\d+)").unwrap()); /// Tag pattern: `#tagname` pub static TAG_PATTERN: Lazy = Lazy::new(|| Regex::new(r"#(\w+)").unwrap()); diff --git a/crates/kogral-core/tests/nickel_integration_test.rs b/crates/kogral-core/tests/nickel_integration_test.rs index caaf4b1..f65f6c8 100644 --- a/crates/kogral-core/tests/nickel_integration_test.rs +++ b/crates/kogral-core/tests/nickel_integration_test.rs @@ -1,6 +1,6 @@ //! Integration tests for Nickel configuration loading -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use kogral_core::config::nickel; use kogral_core::config::schema::{EmbeddingProvider, KbConfig, StorageType}; diff --git a/crates/kogral-mcp/src/auth.rs b/crates/kogral-mcp/src/auth.rs index d5439fe..24fb741 100644 --- a/crates/kogral-mcp/src/auth.rs +++ b/crates/kogral-mcp/src/auth.rs @@ -29,7 +29,8 @@ impl AuthConfig { /// Verify a request token against the configured token /// /// # Arguments - /// * `request_token` - Token provided in the request (can be from params or header) + /// * `request_token` - Token provided in the request (can be from params or + /// header) /// /// # Returns /// * Ok(()) if authentication succeeds diff --git a/crates/kogral-mcp/src/server.rs b/crates/kogral-mcp/src/server.rs index fb2cf50..ba6b4e8 100644 --- a/crates/kogral-mcp/src/server.rs +++ b/crates/kogral-mcp/src/server.rs @@ -7,7 +7,7 @@ use serde_json::{json, Value}; use tracing::{debug, error, info, warn}; use crate::{ - auth::{AuthConfig, extract_token_from_params}, + auth::{extract_token_from_params, AuthConfig}, prompts, resources, tools, types::{ JsonRpcRequest, JsonRpcResponse, PromptsCapability, ResourcesCapability, @@ -31,7 +31,11 @@ impl McpServer { } else { info!("MCP server running without authentication"); } - Self { name, version, auth } + Self { + name, + version, + auth, + } } /// Run the MCP server with stdio transport @@ -96,7 +100,8 @@ impl McpServer { debug!("Handling method: {}", request.method); // Verify authentication for protected methods - // Initialize is allowed without auth for initial setup, ping can be used for health checks + // Initialize is allowed without auth for initial setup, ping can be used for + // health checks if !matches!(request.method.as_str(), "initialize" | "ping") { if let Err(e) = self.authenticate(&request.params) { debug!("Authentication failed: {}", e); diff --git a/crates/kogral-mcp/src/tools.rs b/crates/kogral-mcp/src/tools.rs index 853755a..37e73c0 100644 --- a/crates/kogral-mcp/src/tools.rs +++ b/crates/kogral-mcp/src/tools.rs @@ -358,7 +358,12 @@ async fn tool_add_note(args: Value) -> Result { let mut node = Node::new(NodeType::Note, title); node.content = content; - let tags = validate_string_array(args["tags"].as_array(), "tags", MAX_ARRAY_ITEMS, MAX_SHORT_LENGTH)?; + let tags = validate_string_array( + args["tags"].as_array(), + "tags", + MAX_ARRAY_ITEMS, + MAX_SHORT_LENGTH, + )?; node.tags = tags; let relates_to = validate_string_array( @@ -369,7 +374,9 @@ async fn tool_add_note(args: Value) -> Result { )?; node.relates_to = relates_to; - if let Some(project) = validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)? { + if let Some(project) = + validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)? + { node.project = Some(project); } @@ -393,14 +400,17 @@ async fn tool_add_note(args: Value) -> Result { /// Add an ADR async fn tool_add_decision(args: Value) -> Result { let title = validate_required_string(args["title"].as_str(), "title", MAX_STRING_LENGTH)?; - let decision = validate_required_string(args["decision"].as_str(), "decision", MAX_STRING_LENGTH)?; + let decision = + validate_required_string(args["decision"].as_str(), "decision", MAX_STRING_LENGTH)?; let mut node = Node::new(NodeType::Decision, title); node.content = decision.clone(); // Store decision-specific data in metadata let mut metadata = serde_json::Map::new(); - if let Some(context) = validate_optional_string(args["context"].as_str(), "context", MAX_STRING_LENGTH)? { + if let Some(context) = + validate_optional_string(args["context"].as_str(), "context", MAX_STRING_LENGTH)? + { metadata.insert("context".to_string(), json!(context)); } metadata.insert("decision".to_string(), json!(decision)); @@ -417,10 +427,17 @@ async fn tool_add_decision(args: Value) -> Result { node.metadata = metadata.into_iter().collect(); - let tags = validate_string_array(args["tags"].as_array(), "tags", MAX_ARRAY_ITEMS, MAX_SHORT_LENGTH)?; + let tags = validate_string_array( + args["tags"].as_array(), + "tags", + MAX_ARRAY_ITEMS, + MAX_SHORT_LENGTH, + )?; node.tags = tags; - if let Some(project) = validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)? { + if let Some(project) = + validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)? + { node.project = Some(project); } @@ -448,7 +465,14 @@ async fn tool_link(args: Value) -> Result { let relation_str = validate_enum( args["relation"].as_str(), "relation", - &["relates_to", "depends_on", "implements", "extends", "supersedes", "explains"], + &[ + "relates_to", + "depends_on", + "implements", + "extends", + "supersedes", + "explains", + ], )?; let strength = validate_strength(args["strength"].as_f64())?; @@ -479,8 +503,10 @@ async fn tool_link(args: Value) -> Result { /// Get guidelines async fn tool_get_guidelines(args: Value) -> Result { - let language = validate_optional_string(args["language"].as_str(), "language", MAX_SHORT_LENGTH)?; - let category = validate_optional_string(args["category"].as_str(), "category", MAX_SHORT_LENGTH)?; + let language = + validate_optional_string(args["language"].as_str(), "language", MAX_SHORT_LENGTH)?; + let category = + validate_optional_string(args["category"].as_str(), "category", MAX_SHORT_LENGTH)?; let storage = FilesystemStorage::new(std::path::PathBuf::from(".kogral")); let graph = storage.load_graph("default").await?; @@ -584,8 +610,16 @@ async fn tool_export(args: Value) -> Result { async fn tool_find_blocks(args: Value) -> Result { let tag = validate_optional_string(args["tag"].as_str(), "tag", MAX_SHORT_LENGTH)?; let status = validate_optional_string(args["status"].as_str(), "status", MAX_SHORT_LENGTH)?; - let property_key = validate_optional_string(args["property_key"].as_str(), "property_key", MAX_SHORT_LENGTH)?; - let property_value = validate_optional_string(args["property_value"].as_str(), "property_value", MAX_STRING_LENGTH)?; + let property_key = validate_optional_string( + args["property_key"].as_str(), + "property_key", + MAX_SHORT_LENGTH, + )?; + let property_value = validate_optional_string( + args["property_value"].as_str(), + "property_value", + MAX_STRING_LENGTH, + )?; let limit = validate_limit(args["limit"].as_u64(), 20)?; let storage = FilesystemStorage::new(std::path::PathBuf::from(".kogral")); diff --git a/crates/kogral-mcp/src/validation.rs b/crates/kogral-mcp/src/validation.rs index 43735f2..54e2885 100644 --- a/crates/kogral-mcp/src/validation.rs +++ b/crates/kogral-mcp/src/validation.rs @@ -1,6 +1,7 @@ //! Input validation for MCP tools //! -//! Enforces safe limits on user inputs to prevent abuse and resource exhaustion. +//! Enforces safe limits on user inputs to prevent abuse and resource +//! exhaustion. use anyhow::{anyhow, Result}; use serde_json::Value; @@ -41,7 +42,11 @@ pub fn validate_required_string( } /// Validates an optional string field -pub fn validate_optional_string(value: Option<&str>, field_name: &str, max_length: usize) -> Result> { +pub fn validate_optional_string( + value: Option<&str>, + field_name: &str, + max_length: usize, +) -> Result> { match value { Some(s) => { if s.is_empty() { @@ -82,7 +87,10 @@ pub fn validate_limit(value: Option, default: usize) -> Result { pub fn validate_strength(value: Option) -> Result { let strength = value.unwrap_or(1.0); if !(0.0..=1.0).contains(&strength) { - return Err(anyhow!("Strength must be between 0 and 1, got {}", strength)); + return Err(anyhow!( + "Strength must be between 0 and 1, got {}", + strength + )); } Ok(strength as f32) }