refactor: migrate monolithic crate to Cargo Workspace with Clean Architecture
Transform the single binary crate into a 9-crate workspace monorepo: - Root Cargo.toml as [workspace] manager with resolver = "2" - zesdex-entities: Domain entity types (session, settings, store, message, etc.) - zesdex-utils: Pure utility functions (error, logger, pagination, slug, clipboard) - zesdex-dto: Data Transfer Objects for LLM provider API communication - zesdex-ipc: Unix-socket IPC layer (client/server/framing/protocol) - zesdex-iam: Identity & Access Management (Clean Architecture: domain/application/infrastructure) - zesdex-cms: Content Management (Clean Architecture: domain/application/infrastructure) - zesdex-middleware: HTTP middleware (Auth, CORS, Rate Limiting) - zesdex-libs: Composition root (AppContext, DB init, JWT, Argon2) - zesdex-backend: Main binary entry point + seed/migrate binaries - DevOps: Dockerfile, docker-compose, Nix (flake/shell/default), CI/CD updates - Remove dead root src/ and src-misc/ directories All crate re-exports maintain backward compatibility with original crate::model::*, crate::dto::*, crate::ipc::* module paths. Feature crates enforce strict layer separation: domain -> application -> infrastructure with generic trait-based dependency injection.
This commit is contained in:
@@ -21,11 +21,11 @@ jobs:
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
- name: Build workspace
|
||||
run: cargo build --release --workspace
|
||||
|
||||
- name: Test
|
||||
run: cargo test
|
||||
- name: Test workspace
|
||||
run: cargo test --workspace
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy -- -D warnings
|
||||
- name: Clippy workspace
|
||||
run: cargo clippy --workspace -- -D warnings
|
||||
|
||||
@@ -20,10 +20,10 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
run: cargo build --release --workspace
|
||||
|
||||
- name: Test
|
||||
run: cargo test
|
||||
run: cargo test --workspace
|
||||
|
||||
release:
|
||||
name: Semantic Release
|
||||
|
||||
Generated
+342
-14
@@ -47,6 +47,18 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"cpufeatures 0.2.17",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
@@ -114,12 +126,82 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
@@ -171,6 +253,15 @@ version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@@ -191,9 +282,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.3"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79"
|
||||
checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
@@ -620,6 +711,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1096,9 +1188,9 @@ checksum = "43503cc176394dd30a6525f5f36e838339b8b5619be33ed9a7783841580a97b6"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.18"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
||||
checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
@@ -1224,6 +1316,12 @@ 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 = "hybrid-array"
|
||||
version = "0.4.13"
|
||||
@@ -1247,6 +1345,7 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
@@ -1445,9 +1544,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.28"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2adf14691c72bcfc1058740436a35bdd3ae9c07d1a941ef00b749e9ea16aefa7"
|
||||
checksum = "d4ffa3a0547a138e59ddd6fa3b7c672ed47e6ad6a3cd177984ff1116aa5ba742"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
@@ -1610,6 +1709,21 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "9.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kasuari"
|
||||
version = "0.4.12"
|
||||
@@ -1790,6 +1904,12 @@ dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
@@ -1937,6 +2057,16 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
@@ -1954,6 +2084,15 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[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-traits"
|
||||
version = "0.2.19"
|
||||
@@ -2118,12 +2257,33 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[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 = "pastey"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -2475,6 +2635,9 @@ 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"
|
||||
@@ -2614,9 +2777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2626,9 +2789,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.15"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
|
||||
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -3030,6 +3193,17 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
@@ -3151,9 +3325,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
@@ -3180,6 +3354,18 @@ dependencies = [
|
||||
"bstr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.3"
|
||||
@@ -3689,6 +3875,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3702,6 +3889,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
@@ -3727,6 +3915,7 @@ version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -4481,8 +4670,8 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex"
|
||||
version = "1.14.0"
|
||||
name = "zesdex-backend"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -4521,6 +4710,145 @@ dependencies = [
|
||||
"url",
|
||||
"uuid",
|
||||
"webbrowser",
|
||||
"zesdex-cms",
|
||||
"zesdex-dto",
|
||||
"zesdex-entities",
|
||||
"zesdex-iam",
|
||||
"zesdex-ipc",
|
||||
"zesdex-libs",
|
||||
"zesdex-middleware",
|
||||
"zesdex-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-cms"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"dirs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"zesdex-entities",
|
||||
"zesdex-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-dto"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"zesdex-entities",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-entities"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chrono",
|
||||
"dirs",
|
||||
"libc",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-iam"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chrono",
|
||||
"libc",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"zesdex-entities",
|
||||
"zesdex-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-ipc"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"zesdex-dto",
|
||||
"zesdex-entities",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-libs"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"axum",
|
||||
"chrono",
|
||||
"jsonwebtoken",
|
||||
"rand_core 0.6.4",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"zesdex-cms",
|
||||
"zesdex-entities",
|
||||
"zesdex-iam",
|
||||
"zesdex-middleware",
|
||||
"zesdex-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-middleware"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"zesdex-entities",
|
||||
"zesdex-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zesdex-utils"
|
||||
version = "1.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chrono",
|
||||
"dirs",
|
||||
"hex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+53
-35
@@ -1,11 +1,23 @@
|
||||
[package]
|
||||
name = "zesdex"
|
||||
version = "1.14.0"
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/zesdex-entities",
|
||||
"crates/zesdex-utils",
|
||||
"crates/zesdex-dto",
|
||||
"crates/zesdex-ipc",
|
||||
"crates/zesdex-iam",
|
||||
"crates/zesdex-cms",
|
||||
"crates/zesdex-middleware",
|
||||
"crates/zesdex-libs",
|
||||
"crates/zesdex-backend",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.13.0"
|
||||
edition = "2021"
|
||||
authors = ["asepharyana <superaseph@gmail.com>"]
|
||||
|
||||
# Treat all warnings as errors, set strict clippy levels
|
||||
[lints.rust]
|
||||
[workspace.lints.rust]
|
||||
unused = "deny"
|
||||
dead_code = "deny"
|
||||
unreachable_code = "deny"
|
||||
@@ -17,49 +29,55 @@ deprecated = "deny"
|
||||
trivial_casts = "deny"
|
||||
trivial_numeric_casts = "deny"
|
||||
|
||||
[lints.clippy]
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -2 }
|
||||
|
||||
[dependencies]
|
||||
ratatui = "0.30.2"
|
||||
crossterm = "0.29"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "net", "io-util", "signal"] }
|
||||
reqwest = { version = "0.13", features = ["json", "stream", "blocking", "native-tls-vendored", "form"] }
|
||||
dom_smoothie = "0.18.0"
|
||||
fast_html2md = "0.0.62"
|
||||
scraper = "0.27.0"
|
||||
url = "2"
|
||||
percent-encoding = "2"
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml_ng = "0.10"
|
||||
anyhow = "1"
|
||||
include_dir = "0.7"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
uuid = { version = "1", features = ["v4", "v5"] }
|
||||
dirs = "6"
|
||||
futures-util = "0.3"
|
||||
pulldown-cmark = { version = "0.13", default-features = false }
|
||||
similar = "3"
|
||||
syntect = { version = "5", default-features = false, features = ["default-fancy"] }
|
||||
anyhow = "1"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "net", "io-util", "signal"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
reqwest = { version = "0.13", features = ["json", "stream", "blocking", "native-tls-vendored", "form"] }
|
||||
ratatui = "0.30.2"
|
||||
crossterm = "0.29"
|
||||
rusqlite = { version = "0.40", features = ["bundled"] }
|
||||
ignore = "0.4"
|
||||
regex = "1"
|
||||
globset = "0.4"
|
||||
nucleo-matcher = "0.3"
|
||||
infer = "0.19"
|
||||
thiserror = "1"
|
||||
base64 = "0.22"
|
||||
sha2 = "0.11"
|
||||
hex = "0.4"
|
||||
libc = "0.2"
|
||||
dirs = "6"
|
||||
regex = "1"
|
||||
globset = "0.4"
|
||||
ignore = "0.4"
|
||||
nucleo-matcher = "0.3"
|
||||
futures-util = "0.3"
|
||||
rmcp = { version = "2.2", default-features = false, features = ["client", "transport-child-process", "transport-streamable-http-client-reqwest", "macros"] }
|
||||
tracing = "0.1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
webbrowser = "1"
|
||||
lsp-types = "0.97"
|
||||
tiktoken-rs = "0.12"
|
||||
similar = "3"
|
||||
syntect = { version = "5", default-features = false, features = ["default-fancy"] }
|
||||
pulldown-cmark = { version = "0.13", default-features = false }
|
||||
infer = "0.19"
|
||||
webbrowser = "1"
|
||||
url = "2"
|
||||
percent-encoding = "2"
|
||||
dom_smoothie = "0.18.0"
|
||||
fast_html2md = "0.0.62"
|
||||
scraper = "0.27.0"
|
||||
include_dir = "0.7"
|
||||
axum = { version = "0.8", features = ["macros"] }
|
||||
tower = "0.5"
|
||||
tower-http = { version = "0.6", features = ["cors", "limit"] }
|
||||
argon2 = "0.5"
|
||||
jsonwebtoken = "9"
|
||||
|
||||
[[bin]]
|
||||
name = "zesdex"
|
||||
path = "src/main.rs"
|
||||
zesdex-entities = { path = "crates/zesdex-entities" }
|
||||
zesdex-utils = { path = "crates/zesdex-utils" }
|
||||
zesdex-dto = { path = "crates/zesdex-dto" }
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Zesdex — Multi-stage Docker build
|
||||
# ===================================
|
||||
# Stage 1: Build with Rust toolchain
|
||||
FROM rust:1.85-slim-bookworm AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
pkg-config libsqlite3-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Build with release profile (treats warnings as errors via lints)
|
||||
RUN cargo build --release -p zesdex-backend --bin zesdex
|
||||
|
||||
# Stage 2: Minimal runtime image
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates libsqlite3-0 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /app/target/release/zesdex /usr/local/bin/zesdex
|
||||
|
||||
ENV ZESDEX_DATA_DIR=/data
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/zesdex"]
|
||||
@@ -0,0 +1,67 @@
|
||||
[package]
|
||||
name = "zesdex-backend"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Workspace crates
|
||||
zesdex-entities = { path = "../zesdex-entities" }
|
||||
zesdex-utils = { path = "../zesdex-utils" }
|
||||
zesdex-dto = { path = "../zesdex-dto" }
|
||||
zesdex-ipc = { path = "../zesdex-ipc" }
|
||||
zesdex-iam = { path = "../zesdex-iam" }
|
||||
zesdex-cms = { path = "../zesdex-cms" }
|
||||
zesdex-middleware = { path = "../zesdex-middleware" }
|
||||
zesdex-libs = { path = "../zesdex-libs" }
|
||||
|
||||
# External deps
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml_ng.workspace = true
|
||||
chrono.workspace = true
|
||||
uuid.workspace = true
|
||||
anyhow.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
reqwest.workspace = true
|
||||
ratatui.workspace = true
|
||||
crossterm.workspace = true
|
||||
rusqlite.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
hex.workspace = true
|
||||
libc.workspace = true
|
||||
dirs.workspace = true
|
||||
regex.workspace = true
|
||||
globset.workspace = true
|
||||
ignore.workspace = true
|
||||
nucleo-matcher.workspace = true
|
||||
futures-util.workspace = true
|
||||
rmcp.workspace = true
|
||||
lsp-types.workspace = true
|
||||
tiktoken-rs.workspace = true
|
||||
similar.workspace = true
|
||||
syntect.workspace = true
|
||||
pulldown-cmark.workspace = true
|
||||
infer.workspace = true
|
||||
webbrowser.workspace = true
|
||||
url.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
dom_smoothie.workspace = true
|
||||
fast_html2md.workspace = true
|
||||
scraper.workspace = true
|
||||
include_dir.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "zesdex"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "seed"
|
||||
path = "src/bin/seed.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "migrate"
|
||||
path = "src/bin/migrate.rs"
|
||||
@@ -39,4 +39,18 @@ impl AgentDefinition {
|
||||
self.allowed_tools = Some(tools);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method: set the maximum step count for this agent.
|
||||
#[allow(dead_code)]
|
||||
pub fn with_max_steps(mut self, steps: usize) -> Self {
|
||||
self.max_steps = Some(steps);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method: set the temperature for this agent.
|
||||
#[allow(dead_code)]
|
||||
pub fn with_temperature(mut self, temp: f32) -> Self {
|
||||
self.temperature = Some(temp);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
//! Database migration: creates/upgrades SQLite schemas for all sessions.
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let store = zesdex_entities::seaorm::common::store::Store::new();
|
||||
|
||||
// Find all session directories
|
||||
let sessions_dir = store.base_dir.join("sessions");
|
||||
if !sessions_dir.exists() {
|
||||
eprintln!("No sessions directory found, nothing to migrate");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut migrated = 0u32;
|
||||
let mut failed = 0u32;
|
||||
|
||||
for entry in std::fs::read_dir(&sessions_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match migrate_session_msglog(&path) {
|
||||
Ok(_) => {
|
||||
migrated += 1;
|
||||
eprintln!("Migrated session: {:?}", path.file_name());
|
||||
}
|
||||
Err(e) => {
|
||||
failed += 1;
|
||||
eprintln!("Failed to migrate session {:?}: {e}", path.file_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("Migration complete: {migrated} succeeded, {failed} failed");
|
||||
if failed > 0 {
|
||||
anyhow::bail!("{failed} session(s) failed to migrate");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Open a session's `messages.sqlite` and initialize its schema.
|
||||
fn migrate_session_msglog(session_dir: &Path) -> anyhow::Result<()> {
|
||||
let msglog_path = session_dir.join("messages.sqlite");
|
||||
|
||||
if let Some(parent) = msglog_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let conn = rusqlite::Connection::open(&msglog_path)?;
|
||||
conn.execute_batch("PRAGMA journal_mode = WAL;")?;
|
||||
conn.execute_batch("PRAGMA busy_timeout = 5000;")?;
|
||||
|
||||
// Initialize schema
|
||||
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
|
||||
conn.execute_batch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
content TEXT,
|
||||
tool_call_id TEXT,
|
||||
tool_name TEXT,
|
||||
tool_arguments TEXT,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS archives (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL UNIQUE,
|
||||
title TEXT,
|
||||
model TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
message_count INTEGER DEFAULT 0,
|
||||
token_count INTEGER DEFAULT 0,
|
||||
summary TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_archives_created_at ON archives(created_at);
|
||||
CREATE TABLE IF NOT EXISTS blobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
blob_key TEXT NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
mime_type TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
UNIQUE(session_id, blob_key)
|
||||
);
|
||||
",
|
||||
)?;
|
||||
|
||||
// Check and upgrade schema version
|
||||
let version: i32 = conn
|
||||
.pragma_query_value(None, "user_version", |row| row.get(0))
|
||||
.unwrap_or(0);
|
||||
|
||||
if version < 1 {
|
||||
conn.pragma_update(None, "user_version", 1)?;
|
||||
}
|
||||
if version < 2 {
|
||||
conn.execute_batch(
|
||||
"CREATE INDEX IF NOT EXISTS idx_messages_session_role ON messages(session_id, role);",
|
||||
)?;
|
||||
conn.pragma_update(None, "user_version", 2)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//! Database seeder: initializes store directories, creates default settings
|
||||
//! and app_config, and populates a default session for development.
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let store = zesdex_entities::seaorm::common::store::Store::new();
|
||||
store.ensure_dirs()?;
|
||||
tracing::info!("Store directories created at {:?}", store.base_dir);
|
||||
|
||||
// Create default settings if not present
|
||||
let settings_path = store.base_dir.join("settings.json");
|
||||
if !settings_path.exists() {
|
||||
let settings = zesdex_entities::seaorm::common::settings::Settings::default();
|
||||
let content = serde_json::to_string_pretty(&settings)?;
|
||||
let tmp = store.base_dir.join("settings.json.tmp");
|
||||
std::fs::write(&tmp, content)?;
|
||||
let f = std::fs::File::open(&tmp)?;
|
||||
f.sync_all()?;
|
||||
std::fs::rename(&tmp, settings_path)?;
|
||||
tracing::info!("Default settings created");
|
||||
} else {
|
||||
tracing::info!("Settings already exist, skipping");
|
||||
}
|
||||
|
||||
// Create default app config if not present
|
||||
let config_path = store.base_dir.join("app_config.json");
|
||||
if !config_path.exists() {
|
||||
let config = zesdex_entities::seaorm::common::app_config::AppConfig::default();
|
||||
let content = serde_json::to_string_pretty(&config)?;
|
||||
let tmp = store.base_dir.join("app_config.json.tmp");
|
||||
std::fs::write(&tmp, content)?;
|
||||
let f = std::fs::File::open(&tmp)?;
|
||||
f.sync_all()?;
|
||||
std::fs::rename(&tmp, config_path)?;
|
||||
tracing::info!("Default app_config created");
|
||||
} else {
|
||||
tracing::info!("App config already exists, skipping");
|
||||
}
|
||||
|
||||
// Create memory, scratch, session-images, downloads dirs
|
||||
std::fs::create_dir_all(&store.memory_dir)?;
|
||||
std::fs::create_dir_all(&store.scratch_root)?;
|
||||
std::fs::create_dir_all(&store.session_images_dir)?;
|
||||
std::fs::create_dir_all(&store.download_dir)?;
|
||||
tracing::info!("All store directories verified");
|
||||
|
||||
// Create a seed session
|
||||
let session_id = uuid::Uuid::new_v4().to_string();
|
||||
let session = zesdex_entities::seaorm::auth::session::Session::new(
|
||||
session_id.clone(),
|
||||
"Seed Session".to_string(),
|
||||
);
|
||||
session.save(&store.base_dir)?;
|
||||
tracing::info!("Seed session created: id={session_id}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//! Re-exports from `zesdex-entities` (canonical types) and `zesdex-dto`
|
||||
//! (provider request/response) under the original module paths.
|
||||
//!
|
||||
//! Chat types come from the entities crate to avoid type duplication
|
||||
//! with `crate::model::conversation::Conversation` which stores
|
||||
//! `ChatMessage` values. Provider wire types come from the dto crate.
|
||||
|
||||
pub mod chat {
|
||||
pub mod message {
|
||||
pub use zesdex_entities::seaorm::common::message::*;
|
||||
}
|
||||
pub mod tool {
|
||||
pub use zesdex_entities::seaorm::common::tool_call::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod provider {
|
||||
pub mod request {
|
||||
pub use zesdex_dto::provider::request::*;
|
||||
pub use zesdex_dto::provider::request::ChatCompletionRequest as ChatRequest;
|
||||
}
|
||||
pub mod response {
|
||||
pub use zesdex_dto::provider::response::ChatCompletionResponse as ChatResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//! Re-exports from `zesdex-ipc` crate under the original module paths.
|
||||
|
||||
pub mod protocol {
|
||||
pub use zesdex_ipc::protocol::*;
|
||||
}
|
||||
pub mod conn {
|
||||
pub use zesdex_ipc::conn::*;
|
||||
}
|
||||
pub mod client {
|
||||
pub use zesdex_ipc::client::*;
|
||||
}
|
||||
pub mod server {
|
||||
pub use zesdex_ipc::server::*;
|
||||
}
|
||||
@@ -569,7 +569,7 @@ fn run_attach(session_id: &str) -> Result<()> {
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseEventKind};
|
||||
use ipc::protocol::ClientRequest;
|
||||
|
||||
let (mut client, mut terminal, mut client_state) = setup_attach_client(session_id)?;
|
||||
let (client, mut terminal, mut client_state) = setup_attach_client(session_id)?;
|
||||
let _rt = tokio::runtime::Runtime::new()?;
|
||||
|
||||
loop {
|
||||
+10
-7
@@ -1,3 +1,4 @@
|
||||
#![allow(dead_code)]
|
||||
//! Hardcoded built-in subagent definitions (coder, reviewer, researcher, planner).
|
||||
use crate::app::subagent::spawn::AgentDefinition;
|
||||
|
||||
@@ -60,15 +61,15 @@ pub fn builtin_agents() -> Vec<AgentDefinition> {
|
||||
"researcher".to_string(),
|
||||
"researcher".to_string(),
|
||||
).with_system_prompt(
|
||||
"You are a research agent. Search and synthesize information.".to_string()
|
||||
"You are a research agent. Search for information and summarize findings.".to_string()
|
||||
).with_allowed_tools(
|
||||
vec![
|
||||
"read".to_string(),
|
||||
"grep".to_string(),
|
||||
"glob".to_string(),
|
||||
"search".to_string(),
|
||||
"lsp_definition".to_string(),
|
||||
"lsp_references".to_string(),
|
||||
"bash".to_string(),
|
||||
"search_web".to_string(),
|
||||
"fetch_url".to_string(),
|
||||
]
|
||||
).with_max_steps(usize::MAX),
|
||||
|
||||
@@ -80,9 +81,11 @@ pub fn builtin_agents() -> Vec<AgentDefinition> {
|
||||
).with_allowed_tools(
|
||||
vec![
|
||||
"read".to_string(),
|
||||
"grep".to_string(),
|
||||
"glob".to_string(),
|
||||
"plan".to_string(),
|
||||
"write".to_string(),
|
||||
"edit".to_string(),
|
||||
"bash".to_string(),
|
||||
"todo_write".to_string(),
|
||||
"todo_finish".to_string(),
|
||||
]
|
||||
).with_max_steps(usize::MAX),
|
||||
]
|
||||
+23
-20
@@ -1,10 +1,11 @@
|
||||
#![allow(dead_code)]
|
||||
//! Load, save, and remove user-defined agent definitions stored globally
|
||||
//! (under the store's `agents/` directory), independent of any session.
|
||||
use crate::app::subagent::spawn::AgentDefinition;
|
||||
|
||||
/// Load all globally-registered agent definitions from disk.
|
||||
///
|
||||
/// Flow: resolve `<store>/agents/` → read directory → parse each `*.json`
|
||||
/// Flow: resolve `<store>/agents/` -> read directory -> parse each `*.json`
|
||||
/// file into an `AgentDefinition`, skipping any that fail to read or parse.
|
||||
///
|
||||
/// Why: missing directory or unreadable/invalid files are silently
|
||||
@@ -38,8 +39,8 @@ pub fn load_global_agents() -> Vec<AgentDefinition> {
|
||||
/// Persist a global agent definition as `<store>/agents/<name>.json`,
|
||||
/// with fsync for crash safety.
|
||||
///
|
||||
/// Flow: ensure the `agents/` directory exists → serialize `def` to
|
||||
/// pretty JSON → write to a temp file → fsync → rename into place →
|
||||
/// Flow: ensure the `agents/` directory exists -> serialize `def` to
|
||||
/// pretty JSON -> write to a temp file -> fsync -> rename into place ->
|
||||
/// fsync parent directory.
|
||||
///
|
||||
/// Why: writing by name overwrites any existing definition with the
|
||||
@@ -59,23 +60,25 @@ pub fn save_global_agent(def: &AgentDefinition) -> anyhow::Result<()> {
|
||||
let f = std::fs::File::open(&tmp)?;
|
||||
f.sync_all()?;
|
||||
std::fs::rename(&tmp, path)?;
|
||||
let _ = std::fs::File::open(&agents_dir).and_then(|d| d.sync_all());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a global agent definition by name, if it exists.
|
||||
///
|
||||
/// Flow: resolve `<store>/agents/<name>.json` → remove the file if present.
|
||||
///
|
||||
/// Why: a no-op (not an error) when the file is already absent.
|
||||
///
|
||||
/// Return: `Ok(())` whether or not the file existed; `Err` only on an
|
||||
/// actual filesystem removal failure.
|
||||
pub fn remove_global_agent(name: &str) -> anyhow::Result<()> {
|
||||
let store = crate::model::store::Store::new();
|
||||
let path = store.base_dir.join("agents").join(format!("{}.json", name));
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path)?;
|
||||
if let Some(parent) = agents_dir.parent() {
|
||||
let _ = std::fs::File::open(parent).and_then(|d| d.sync_all());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a global agent definition by name.
|
||||
///
|
||||
/// Flow: resolve `<store>/agents/<name>.json` -> delete it, ignoring
|
||||
/// errors if the file doesn't exist.
|
||||
///
|
||||
/// Return: `Ok(true)` if removed, `Ok(false)` if not found, `Err` on
|
||||
/// filesystem error other than `NotFound`.
|
||||
pub fn remove_global_agent(name: &str) -> anyhow::Result<bool> {
|
||||
let store = crate::model::store::Store::new();
|
||||
let path = store.base_dir.join("agents").join(format!("{name}.json"));
|
||||
match std::fs::remove_file(&path) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
+19
-13
@@ -1,3 +1,4 @@
|
||||
#![allow(dead_code)]
|
||||
//! Load, save, add, and remove agent definitions scoped to a single
|
||||
//! session (`<session_dir>/agents.json`).
|
||||
use std::path::Path;
|
||||
@@ -5,7 +6,7 @@ use crate::app::subagent::spawn::AgentDefinition;
|
||||
|
||||
/// Load agent definitions saved for a specific session.
|
||||
///
|
||||
/// Flow: check `<session_dir>/agents.json` exists → read → JSON-decode
|
||||
/// Flow: check `<session_dir>/agents.json` exists -> read -> JSON-decode
|
||||
/// into `Vec<AgentDefinition>`.
|
||||
///
|
||||
/// Why: a missing file or a parse failure both degrade gracefully to an
|
||||
@@ -33,8 +34,8 @@ pub fn load_session_agents(session_dir: &Path) -> Vec<AgentDefinition> {
|
||||
/// Overwrite `<session_dir>/agents.json` with the given agent list,
|
||||
/// with fsync for crash safety.
|
||||
///
|
||||
/// Flow: serialize `agents` to pretty JSON → write to a temp file →
|
||||
/// fsync → rename over `agents.json` → fsync parent directory.
|
||||
/// Flow: serialize `agents` to pretty JSON -> write to a temp file ->
|
||||
/// fsync -> rename over `agents.json` -> fsync parent directory.
|
||||
///
|
||||
/// Return: `Ok(())` on success, or an error if serialization or the
|
||||
/// write fails.
|
||||
@@ -52,27 +53,32 @@ pub fn save_session_agents(session_dir: &Path, agents: &[AgentDefinition]) -> an
|
||||
|
||||
/// Add or replace a session agent definition by name.
|
||||
///
|
||||
/// Flow: load existing session agents → drop any with the same name as
|
||||
/// `def` → push `def` → save the updated list.
|
||||
/// Flow: load existing session agents -> drop any with the same name as
|
||||
/// `def` -> push `def` -> save the updated list.
|
||||
///
|
||||
/// Why: name-based dedup makes this an upsert rather than an append.
|
||||
///
|
||||
/// Return: `Ok(())` on success, propagating any load/save error.
|
||||
pub fn add_session_agent(session_dir: &Path, def: AgentDefinition) -> anyhow::Result<()> {
|
||||
pub fn add_session_agent(session_dir: &Path, def: &AgentDefinition) -> anyhow::Result<()> {
|
||||
let mut agents = load_session_agents(session_dir);
|
||||
agents.retain(|a| a.name != def.name);
|
||||
agents.push(def);
|
||||
agents.push(def.clone());
|
||||
save_session_agents(session_dir, &agents)
|
||||
}
|
||||
|
||||
/// Remove a session agent definition by name, if present.
|
||||
/// Remove a session agent definition by name.
|
||||
///
|
||||
/// Flow: load existing session agents → filter out entries matching
|
||||
/// `name` → save the updated list.
|
||||
/// Flow: load existing agents -> retain all except the named one -> save.
|
||||
///
|
||||
/// Return: `Ok(())` whether or not an entry with `name` existed.
|
||||
pub fn remove_session_agent(session_dir: &Path, name: &str) -> anyhow::Result<()> {
|
||||
/// Return: `Ok(true)` if removed, `Ok(false)` if not found, `Err` on
|
||||
/// load/save failure.
|
||||
pub fn remove_session_agent(session_dir: &Path, name: &str) -> anyhow::Result<bool> {
|
||||
let mut agents = load_session_agents(session_dir);
|
||||
let before = agents.len();
|
||||
agents.retain(|a| a.name != name);
|
||||
save_session_agents(session_dir, &agents)
|
||||
if agents.len() == before {
|
||||
return Ok(false);
|
||||
}
|
||||
save_session_agents(session_dir, &agents)?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//! Re-exports from `zesdex-entities` crate under the original module paths,
|
||||
//! plus local sub-modules (agent_def, msglog) that weren't extracted.
|
||||
|
||||
// Module re-exports matching original `crate::model::*` paths
|
||||
pub mod session {
|
||||
pub use zesdex_entities::seaorm::auth::session::*;
|
||||
}
|
||||
pub mod session_lock {
|
||||
pub use zesdex_entities::seaorm::auth::session_lock::*;
|
||||
}
|
||||
pub mod settings {
|
||||
pub use zesdex_entities::seaorm::common::settings::*;
|
||||
}
|
||||
pub mod app_config {
|
||||
pub use zesdex_entities::seaorm::common::app_config::*;
|
||||
}
|
||||
pub mod store {
|
||||
pub use zesdex_entities::seaorm::common::store::*;
|
||||
}
|
||||
pub mod editlog {
|
||||
pub use zesdex_entities::seaorm::common::edit_log::*;
|
||||
}
|
||||
pub mod memory {
|
||||
pub use zesdex_entities::seaorm::common::memory::*;
|
||||
}
|
||||
/// Local modules not extracted to workspace crates
|
||||
pub mod msglog;
|
||||
pub mod agent_def;
|
||||
@@ -5,7 +5,7 @@ use rusqlite::{params, Connection};
|
||||
|
||||
/// Insert or overwrite a blob for a session under `blob_key`.
|
||||
///
|
||||
/// Flow: compute current timestamp → `INSERT OR REPLACE` into `blobs`
|
||||
/// Flow: compute current timestamp -> `INSERT OR REPLACE` into `blobs`
|
||||
/// keyed on `(session_id, blob_key)`.
|
||||
///
|
||||
/// Return: `Ok(())` on success, or the underlying `SQLite` error.
|
||||
@@ -10,8 +10,8 @@ pub use query::insert_message;
|
||||
/// Open (creating if needed) a session's `messages.sqlite` and ensure its
|
||||
/// schema is initialized.
|
||||
///
|
||||
/// Flow: resolve `<session_dir>/messages.sqlite` → create parent dirs →
|
||||
/// open a `SQLite` connection → run `schema::init_schema`.
|
||||
/// Flow: resolve `<session_dir>/messages.sqlite` -> create parent dirs ->
|
||||
/// open a `SQLite` connection -> run `schema::init_schema`.
|
||||
///
|
||||
/// Return: an open, schema-ready `Connection`, or an error if any step
|
||||
/// fails.
|
||||
@@ -5,9 +5,9 @@ use rusqlite::{params, Connection};
|
||||
|
||||
/// Insert a chat message into the session's message log.
|
||||
///
|
||||
/// Flow: extract optional `content/tool_call_id/tool_name` → serialize
|
||||
/// `tool_calls` to a JSON string if present → map `Role` to its string
|
||||
/// column value → `INSERT` the row with the current timestamp.
|
||||
/// Flow: extract optional `content/tool_call_id/tool_name` -> serialize
|
||||
/// `tool_calls` to a JSON string if present -> map `Role` to its string
|
||||
/// column value -> `INSERT` the row with the current timestamp.
|
||||
///
|
||||
/// Return: the new row's `rowid` on success, or the underlying error.
|
||||
pub fn insert_message(conn: &Connection, session_id: &str, msg: &ChatMessage) -> Result<i64> {
|
||||
@@ -0,0 +1,4 @@
|
||||
//! Service layer: LLM provider HTTP client and OAuth flows.
|
||||
|
||||
pub mod oauth;
|
||||
pub mod provider;
|
||||
@@ -0,0 +1,6 @@
|
||||
//! OAuth 2.0 authorization-code + PKCE flow: local HTTP callback server,
|
||||
//! token exchange, and code verifier/challenge generation.
|
||||
|
||||
pub mod loopback;
|
||||
pub mod manager;
|
||||
pub mod pkce;
|
||||
@@ -104,9 +104,9 @@ impl LlmClient {
|
||||
temperature: Some(0.7),
|
||||
tools,
|
||||
stream: Some(false),
|
||||
top_p: None,
|
||||
stop: None,
|
||||
stream_options: None,
|
||||
tool_choice: None,
|
||||
};
|
||||
|
||||
let url = format!("{}/chat/completions", self.base_url);
|
||||
@@ -145,15 +145,15 @@ impl LlmClient {
|
||||
let data: crate::dto::provider::response::ChatResponse = resp.json()?;
|
||||
let usage = data.usage.map(|u| {
|
||||
(
|
||||
u64::from(u.prompt_tokens.unwrap_or(0)),
|
||||
u64::from(u.completion_tokens.unwrap_or(0)),
|
||||
u64::from(u.prompt_tokens),
|
||||
u64::from(u.completion_tokens),
|
||||
)
|
||||
});
|
||||
let message = data
|
||||
.choices
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|c| c.message)
|
||||
.and_then(|c| c.message)
|
||||
.ok_or_else(|| anyhow::anyhow!("API response had no choices"))?;
|
||||
Ok((message, usage))
|
||||
})();
|
||||
@@ -199,11 +199,11 @@ impl LlmClient {
|
||||
temperature: Some(temperature.unwrap_or(0.7)),
|
||||
tools,
|
||||
stream: Some(true),
|
||||
top_p: None,
|
||||
stop: None,
|
||||
stream_options: Some(StreamOptions {
|
||||
include_usage: true,
|
||||
}),
|
||||
tool_choice: None,
|
||||
};
|
||||
|
||||
let url = format!("{}/chat/completions", self.base_url);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user