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:
asepharyana
2026-07-17 09:08:41 +07:00
parent 86cc412395
commit be0a9582bb
248 changed files with 7901 additions and 1505 deletions
+6 -6
View File
@@ -21,11 +21,11 @@ jobs:
with: with:
components: clippy components: clippy
- name: Build - name: Build workspace
run: cargo build --release run: cargo build --release --workspace
- name: Test - name: Test workspace
run: cargo test run: cargo test --workspace
- name: Clippy - name: Clippy workspace
run: cargo clippy -- -D warnings run: cargo clippy --workspace -- -D warnings
+2 -2
View File
@@ -20,10 +20,10 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Build - name: Build
run: cargo build --release run: cargo build --release --workspace
- name: Test - name: Test
run: cargo test run: cargo test --workspace
release: release:
name: Semantic Release name: Semantic Release
Generated
+342 -14
View File
@@ -47,6 +47,18 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.89" version = "0.1.89"
@@ -114,12 +126,82 @@ dependencies = [
"pkg-config", "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]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.3" version = "1.3.3"
@@ -171,6 +253,15 @@ version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@@ -191,9 +282,9 @@ dependencies = [
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.3" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
dependencies = [ dependencies = [
"memchr", "memchr",
"regex-automata", "regex-automata",
@@ -620,6 +711,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer 0.10.4", "block-buffer 0.10.4",
"crypto-common 0.1.7", "crypto-common 0.1.7",
"subtle",
] ]
[[package]] [[package]]
@@ -1096,9 +1188,9 @@ checksum = "43503cc176394dd30a6525f5f36e838339b8b5619be33ed9a7783841580a97b6"
[[package]] [[package]]
name = "globset" name = "globset"
version = "0.4.18" version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"bstr", "bstr",
@@ -1224,6 +1316,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.13" version = "0.4.13"
@@ -1247,6 +1345,7 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
"httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"smallvec", "smallvec",
@@ -1445,9 +1544,9 @@ dependencies = [
[[package]] [[package]]
name = "ignore" name = "ignore"
version = "0.4.28" version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2adf14691c72bcfc1058740436a35bdd3ae9c07d1a941ef00b749e9ea16aefa7" checksum = "d4ffa3a0547a138e59ddd6fa3b7c672ed47e6ad6a3cd177984ff1116aa5ba742"
dependencies = [ dependencies = [
"crossbeam-deque", "crossbeam-deque",
"globset", "globset",
@@ -1610,6 +1709,21 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "kasuari" name = "kasuari"
version = "0.4.12" version = "0.4.12"
@@ -1790,6 +1904,12 @@ dependencies = [
"regex-automata", "regex-automata",
] ]
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.3" version = "2.8.3"
@@ -1937,6 +2057,16 @@ dependencies = [
"unicode-segmentation", "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]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.2" version = "0.2.2"
@@ -1954,6 +2084,15 @@ dependencies = [
"syn 2.0.118", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -2118,12 +2257,33 @@ dependencies = [
"windows-link", "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]] [[package]]
name = "pastey" name = "pastey"
version = "0.2.3" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" 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]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.2" version = "2.3.2"
@@ -2475,6 +2635,9 @@ name = "rand_core"
version = "0.6.4" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
@@ -2614,9 +2777,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.13.0" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -2626,9 +2789,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.15" version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -3030,6 +3193,17 @@ dependencies = [
"zmij", "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]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.20" version = "0.1.20"
@@ -3151,9 +3325,9 @@ dependencies = [
[[package]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.9" version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]] [[package]]
name = "simd_cesu8" name = "simd_cesu8"
@@ -3180,6 +3354,18 @@ dependencies = [
"bstr", "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]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.3" version = "1.0.3"
@@ -3689,6 +3875,7 @@ dependencies = [
"tokio", "tokio",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing",
] ]
[[package]] [[package]]
@@ -3702,6 +3889,7 @@ dependencies = [
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
"http-body-util",
"pin-project-lite", "pin-project-lite",
"tower", "tower",
"tower-layer", "tower-layer",
@@ -3727,6 +3915,7 @@ version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [ dependencies = [
"log",
"pin-project-lite", "pin-project-lite",
"tracing-attributes", "tracing-attributes",
"tracing-core", "tracing-core",
@@ -4481,8 +4670,8 @@ dependencies = [
] ]
[[package]] [[package]]
name = "zesdex" name = "zesdex-backend"
version = "1.14.0" version = "1.13.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -4521,6 +4710,145 @@ dependencies = [
"url", "url",
"uuid", "uuid",
"webbrowser", "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]] [[package]]
+53 -35
View File
@@ -1,11 +1,23 @@
[package] [workspace]
name = "zesdex" resolver = "2"
version = "1.14.0" 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" edition = "2021"
authors = ["asepharyana <superaseph@gmail.com>"] authors = ["asepharyana <superaseph@gmail.com>"]
# Treat all warnings as errors, set strict clippy levels [workspace.lints.rust]
[lints.rust]
unused = "deny" unused = "deny"
dead_code = "deny" dead_code = "deny"
unreachable_code = "deny" unreachable_code = "deny"
@@ -17,49 +29,55 @@ deprecated = "deny"
trivial_casts = "deny" trivial_casts = "deny"
trivial_numeric_casts = "deny" trivial_numeric_casts = "deny"
[lints.clippy] [workspace.lints.clippy]
all = { level = "warn", priority = -1 } all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -2 } pedantic = { level = "warn", priority = -2 }
[dependencies] [workspace.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"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
serde_yaml_ng = "0.10" serde_yaml_ng = "0.10"
anyhow = "1" chrono = { version = "0.4", features = ["serde"] }
include_dir = "0.7"
uuid = { version = "1", features = ["v4", "v5"] } uuid = { version = "1", features = ["v4", "v5"] }
dirs = "6" anyhow = "1"
futures-util = "0.3" tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "net", "io-util", "signal"] }
pulldown-cmark = { version = "0.13", default-features = false } tracing = "0.1"
similar = "3" tracing-subscriber = { version = "0.3", features = ["env-filter"] }
syntect = { version = "5", default-features = false, features = ["default-fancy"] } 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"] } rusqlite = { version = "0.40", features = ["bundled"] }
ignore = "0.4" thiserror = "1"
regex = "1"
globset = "0.4"
nucleo-matcher = "0.3"
infer = "0.19"
base64 = "0.22" base64 = "0.22"
sha2 = "0.11" sha2 = "0.11"
hex = "0.4" hex = "0.4"
libc = "0.2" 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"] } 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" lsp-types = "0.97"
tiktoken-rs = "0.12" 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]] zesdex-entities = { path = "crates/zesdex-entities" }
name = "zesdex" zesdex-utils = { path = "crates/zesdex-utils" }
path = "src/main.rs" zesdex-dto = { path = "crates/zesdex-dto" }
+30
View File
@@ -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"]
+67
View File
@@ -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.allowed_tools = Some(tools);
self 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
}
} }
+111
View File
@@ -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(())
}
+56
View File
@@ -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(())
}
+25
View File
@@ -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;
}
}
+14
View File
@@ -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 crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseEventKind};
use ipc::protocol::ClientRequest; 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()?; let _rt = tokio::runtime::Runtime::new()?;
loop { loop {
@@ -1,3 +1,4 @@
#![allow(dead_code)]
//! Hardcoded built-in subagent definitions (coder, reviewer, researcher, planner). //! Hardcoded built-in subagent definitions (coder, reviewer, researcher, planner).
use crate::app::subagent::spawn::AgentDefinition; use crate::app::subagent::spawn::AgentDefinition;
@@ -60,15 +61,15 @@ pub fn builtin_agents() -> Vec<AgentDefinition> {
"researcher".to_string(), "researcher".to_string(),
"researcher".to_string(), "researcher".to_string(),
).with_system_prompt( ).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( ).with_allowed_tools(
vec![ vec![
"read".to_string(), "read".to_string(),
"grep".to_string(), "grep".to_string(),
"glob".to_string(), "glob".to_string(),
"search".to_string(), "bash".to_string(),
"lsp_definition".to_string(), "search_web".to_string(),
"lsp_references".to_string(), "fetch_url".to_string(),
] ]
).with_max_steps(usize::MAX), ).with_max_steps(usize::MAX),
@@ -80,9 +81,11 @@ pub fn builtin_agents() -> Vec<AgentDefinition> {
).with_allowed_tools( ).with_allowed_tools(
vec![ vec![
"read".to_string(), "read".to_string(),
"grep".to_string(), "write".to_string(),
"glob".to_string(), "edit".to_string(),
"plan".to_string(), "bash".to_string(),
"todo_write".to_string(),
"todo_finish".to_string(),
] ]
).with_max_steps(usize::MAX), ).with_max_steps(usize::MAX),
] ]
@@ -1,10 +1,11 @@
#![allow(dead_code)]
//! Load, save, and remove user-defined agent definitions stored globally //! Load, save, and remove user-defined agent definitions stored globally
//! (under the store's `agents/` directory), independent of any session. //! (under the store's `agents/` directory), independent of any session.
use crate::app::subagent::spawn::AgentDefinition; use crate::app::subagent::spawn::AgentDefinition;
/// Load all globally-registered agent definitions from disk. /// 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. /// file into an `AgentDefinition`, skipping any that fail to read or parse.
/// ///
/// Why: missing directory or unreadable/invalid files are silently /// 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`, /// Persist a global agent definition as `<store>/agents/<name>.json`,
/// with fsync for crash safety. /// with fsync for crash safety.
/// ///
/// Flow: ensure the `agents/` directory exists serialize `def` to /// Flow: ensure the `agents/` directory exists -> serialize `def` to
/// pretty JSON write to a temp file fsync rename into place /// pretty JSON -> write to a temp file -> fsync -> rename into place ->
/// fsync parent directory. /// fsync parent directory.
/// ///
/// Why: writing by name overwrites any existing definition with the /// 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)?; let f = std::fs::File::open(&tmp)?;
f.sync_all()?; f.sync_all()?;
std::fs::rename(&tmp, path)?; std::fs::rename(&tmp, path)?;
let _ = std::fs::File::open(&agents_dir).and_then(|d| d.sync_all()); if let Some(parent) = agents_dir.parent() {
Ok(()) let _ = std::fs::File::open(parent).and_then(|d| d.sync_all());
}
/// 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)?;
} }
Ok(()) 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()),
}
}
@@ -1,3 +1,4 @@
#![allow(dead_code)]
//! Load, save, add, and remove agent definitions scoped to a single //! Load, save, add, and remove agent definitions scoped to a single
//! session (`<session_dir>/agents.json`). //! session (`<session_dir>/agents.json`).
use std::path::Path; use std::path::Path;
@@ -5,7 +6,7 @@ use crate::app::subagent::spawn::AgentDefinition;
/// Load agent definitions saved for a specific session. /// 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>`. /// into `Vec<AgentDefinition>`.
/// ///
/// Why: a missing file or a parse failure both degrade gracefully to an /// 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, /// Overwrite `<session_dir>/agents.json` with the given agent list,
/// with fsync for crash safety. /// with fsync for crash safety.
/// ///
/// Flow: serialize `agents` to pretty JSON write to a temp file /// Flow: serialize `agents` to pretty JSON -> write to a temp file ->
/// fsync rename over `agents.json` fsync parent directory. /// fsync -> rename over `agents.json` -> fsync parent directory.
/// ///
/// Return: `Ok(())` on success, or an error if serialization or the /// Return: `Ok(())` on success, or an error if serialization or the
/// write fails. /// 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. /// Add or replace a session agent definition by name.
/// ///
/// Flow: load existing session agents drop any with the same name as /// Flow: load existing session agents -> drop any with the same name as
/// `def` push `def` save the updated list. /// `def` -> push `def` -> save the updated list.
/// ///
/// Why: name-based dedup makes this an upsert rather than an append. /// Why: name-based dedup makes this an upsert rather than an append.
/// ///
/// Return: `Ok(())` on success, propagating any load/save error. /// 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); let mut agents = load_session_agents(session_dir);
agents.retain(|a| a.name != def.name); agents.retain(|a| a.name != def.name);
agents.push(def); agents.push(def.clone());
save_session_agents(session_dir, &agents) 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 /// Flow: load existing agents -> retain all except the named one -> save.
/// `name` → save the updated list.
/// ///
/// Return: `Ok(())` whether or not an entry with `name` existed. /// Return: `Ok(true)` if removed, `Ok(false)` if not found, `Err` on
pub fn remove_session_agent(session_dir: &Path, name: &str) -> anyhow::Result<()> { /// 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 mut agents = load_session_agents(session_dir);
let before = agents.len();
agents.retain(|a| a.name != name); 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)
} }
+28
View File
@@ -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`. /// 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)`. /// keyed on `(session_id, blob_key)`.
/// ///
/// Return: `Ok(())` on success, or the underlying `SQLite` error. /// 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 /// Open (creating if needed) a session's `messages.sqlite` and ensure its
/// schema is initialized. /// schema is initialized.
/// ///
/// Flow: resolve `<session_dir>/messages.sqlite` create parent dirs /// Flow: resolve `<session_dir>/messages.sqlite` -> create parent dirs ->
/// open a `SQLite` connection run `schema::init_schema`. /// open a `SQLite` connection -> run `schema::init_schema`.
/// ///
/// Return: an open, schema-ready `Connection`, or an error if any step /// Return: an open, schema-ready `Connection`, or an error if any step
/// fails. /// fails.
@@ -5,9 +5,9 @@ use rusqlite::{params, Connection};
/// Insert a chat message into the session's message log. /// Insert a chat message into the session's message log.
/// ///
/// Flow: extract optional `content/tool_call_id/tool_name` serialize /// Flow: extract optional `content/tool_call_id/tool_name` -> serialize
/// `tool_calls` to a JSON string if present map `Role` to its string /// `tool_calls` to a JSON string if present -> map `Role` to its string
/// column value `INSERT` the row with the current timestamp. /// column value -> `INSERT` the row with the current timestamp.
/// ///
/// Return: the new row's `rowid` on success, or the underlying error. /// 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> { pub fn insert_message(conn: &Connection, session_id: &str, msg: &ChatMessage) -> Result<i64> {
+4
View File
@@ -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), temperature: Some(0.7),
tools, tools,
stream: Some(false), stream: Some(false),
top_p: None,
stop: None, stop: None,
stream_options: None, stream_options: None,
tool_choice: None,
}; };
let url = format!("{}/chat/completions", self.base_url); let url = format!("{}/chat/completions", self.base_url);
@@ -145,15 +145,15 @@ impl LlmClient {
let data: crate::dto::provider::response::ChatResponse = resp.json()?; let data: crate::dto::provider::response::ChatResponse = resp.json()?;
let usage = data.usage.map(|u| { let usage = data.usage.map(|u| {
( (
u64::from(u.prompt_tokens.unwrap_or(0)), u64::from(u.prompt_tokens),
u64::from(u.completion_tokens.unwrap_or(0)), u64::from(u.completion_tokens),
) )
}); });
let message = data let message = data
.choices .choices
.into_iter() .into_iter()
.next() .next()
.map(|c| c.message) .and_then(|c| c.message)
.ok_or_else(|| anyhow::anyhow!("API response had no choices"))?; .ok_or_else(|| anyhow::anyhow!("API response had no choices"))?;
Ok((message, usage)) Ok((message, usage))
})(); })();
@@ -199,11 +199,11 @@ impl LlmClient {
temperature: Some(temperature.unwrap_or(0.7)), temperature: Some(temperature.unwrap_or(0.7)),
tools, tools,
stream: Some(true), stream: Some(true),
top_p: None,
stop: None, stop: None,
stream_options: Some(StreamOptions { stream_options: Some(StreamOptions {
include_usage: true, include_usage: true,
}), }),
tool_choice: None,
}; };
let url = format!("{}/chat/completions", self.base_url); let url = format!("{}/chat/completions", self.base_url);

Some files were not shown because too many files have changed in this diff Show More