Integrate OpenRouter for AI-powered code review
Add openrouter-rs dependency, review prompt, and markdown formatting. Update comment API to accept dynamic body. Adjust devcontainer for podman compatibility.
This commit is contained in:
@@ -12,18 +12,8 @@
|
||||
"containerEnv": {
|
||||
"SHELL": "/bin/bash"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml", "fill-labs.dependi"],
|
||||
"settings": {
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/herald,type=bind",
|
||||
"workspaceFolder": "/workspaces/herald",
|
||||
"runArgs": ["--userns=keep-id", "--security-opt", "label=disable"],
|
||||
"appPort": [3000]
|
||||
}
|
||||
|
||||
Generated
+314
-18
@@ -229,6 +229,72 @@ dependencies = [
|
||||
"cmov",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
@@ -249,7 +315,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -258,12 +324,30 @@ version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy_macro"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0235d912a8c749f4e0c9f18ca253b4c28cfefc1d2518096016d6e3230b6424"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
@@ -331,6 +415,23 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.32"
|
||||
@@ -350,7 +451,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
@@ -417,12 +522,13 @@ dependencies = [
|
||||
"dotenvy",
|
||||
"hex",
|
||||
"hmac",
|
||||
"reqwest",
|
||||
"openrouter-rs",
|
||||
"reqwest 0.13.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -530,6 +636,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -639,6 +746,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
@@ -694,7 +807,7 @@ dependencies = [
|
||||
"jni-sys",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"windows-link",
|
||||
]
|
||||
@@ -709,7 +822,7 @@ dependencies = [
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"simd_cesu8",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -728,7 +841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -821,6 +934,26 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "openrouter-rs"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7101578df2f54d9013594e94367adbe656521ef6002f03e4577f1e00e57cb4"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"dotenvy_macro",
|
||||
"futures-util",
|
||||
"http",
|
||||
"reqwest 0.12.28",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
@@ -903,7 +1036,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -925,7 +1058,7 @@ dependencies = [
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror",
|
||||
"thiserror 2.0.18",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -998,6 +1131,67 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
||||
dependencies = [
|
||||
"ref-cast-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast-impl"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.3"
|
||||
@@ -1075,6 +1269,7 @@ checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
@@ -1172,6 +1367,31 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"ref-cast",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -1234,7 +1454,18 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1344,12 +1575,29 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -1378,7 +1626,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1402,13 +1650,33 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1419,7 +1687,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1472,7 +1740,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1600,6 +1868,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
@@ -1682,7 +1956,7 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -1695,6 +1969,19 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.98"
|
||||
@@ -1724,6 +2011,15 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
@@ -1955,7 +2251,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -1976,7 +2272,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1996,7 +2292,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -2036,7 +2332,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -8,6 +8,7 @@ reqwest = { version = "0.13", features = ["json"] }
|
||||
tokio = { version = "1.52", features = ["full"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
openrouter-rs = "0.10"
|
||||
dotenvy = "0.15"
|
||||
axum = "0.8"
|
||||
anyhow = "1.0"
|
||||
|
||||
+135
-6
@@ -1,22 +1,81 @@
|
||||
use std::time::Duration;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
consts::{BOT_PROCESS_MSG, REVIEW_PROMPT},
|
||||
env::EnvConfig,
|
||||
errors::AppError,
|
||||
gitea::{GiteaAPI, ReviewPayload, WebhookType},
|
||||
open_router::OpenRouterClient,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ReviewResult {
|
||||
reviews: Vec<ReviewItem>,
|
||||
comment: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ReviewItem {
|
||||
filename: String,
|
||||
line: Option<u64>,
|
||||
code: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
/// Map a filename to a markdown language identifier for syntax highlighting.
|
||||
fn lang_from_filename(filename: &str) -> &str {
|
||||
match std::path::Path::new(filename)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("")
|
||||
{
|
||||
"rs" => "rust",
|
||||
"py" => "python",
|
||||
"js" | "mjs" => "javascript",
|
||||
"ts" => "typescript",
|
||||
"jsx" => "jsx",
|
||||
"tsx" => "tsx",
|
||||
"go" => "go",
|
||||
"java" => "java",
|
||||
"kt" | "kts" => "kotlin",
|
||||
"scala" => "scala",
|
||||
"c" | "h" => "c",
|
||||
"cpp" | "cc" | "cxx" | "hpp" | "hxx" => "cpp",
|
||||
"rb" => "ruby",
|
||||
"php" => "php",
|
||||
"swift" => "swift",
|
||||
"sh" | "bash" | "zsh" => "bash",
|
||||
"sql" => "sql",
|
||||
"html" | "htm" => "html",
|
||||
"css" => "css",
|
||||
"scss" | "sass" => "scss",
|
||||
"json" => "json",
|
||||
"yaml" | "yml" => "yaml",
|
||||
"xml" => "xml",
|
||||
"toml" => "toml",
|
||||
"md" | "mdx" => "markdown",
|
||||
"dockerfile" | "Dockerfile" => "dockerfile",
|
||||
"Makefile" => "makefile",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bot {
|
||||
config: EnvConfig,
|
||||
gitea_api: GiteaAPI,
|
||||
open_router_client: OpenRouterClient,
|
||||
}
|
||||
|
||||
impl Bot {
|
||||
pub fn new(config: EnvConfig) -> Self {
|
||||
Self {
|
||||
pub fn new(config: EnvConfig) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
gitea_api: GiteaAPI::new(&config.gitea_url, &config.gitea_token),
|
||||
open_router_client: OpenRouterClient::new(
|
||||
&config.open_router_api_key,
|
||||
&config.open_router_model,
|
||||
)?,
|
||||
config,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn start(
|
||||
@@ -46,17 +105,87 @@ impl Bot {
|
||||
let new_comment = self
|
||||
.gitea_api
|
||||
.comment(
|
||||
&BOT_PROCESS_MSG.replace("{model}", &self.config.open_router_model),
|
||||
&review_payload.repository.full_name,
|
||||
review_payload.pull_request.number,
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
let bot_result: Result<String, anyhow::Error> = async {
|
||||
let git_diff = self
|
||||
.download_git_diff(&review_payload.pull_request.diff_url)
|
||||
.await?;
|
||||
|
||||
let bot_request = &&REVIEW_PROMPT
|
||||
.replace("{subject}", &review_payload.pull_request.title)
|
||||
.replace("{comment}", &review_payload.comment.body)
|
||||
.replace("{diff}", &git_diff);
|
||||
|
||||
self.open_router_client.chat(&bot_request).await
|
||||
}
|
||||
.await;
|
||||
|
||||
let edit_msg = match bot_result {
|
||||
Ok(bot_result) => self.review_result_to_markdown(&bot_result),
|
||||
Err(e) => format!("Error while reviewing: {}", e),
|
||||
};
|
||||
|
||||
self.gitea_api
|
||||
.edit_comment(&review_payload.repository.full_name, new_comment.id)
|
||||
.edit_comment(
|
||||
&edit_msg,
|
||||
&review_payload.repository.full_name,
|
||||
new_comment.id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn review_result_to_markdown(&self, result: &str) -> String {
|
||||
let review_result: ReviewResult = match serde_json::from_str(result) {
|
||||
Ok(review_result) => review_result,
|
||||
Err(_) => {
|
||||
return format!(
|
||||
"Failed to parse review result. Raw output:\n\n```json\n{}\n```",
|
||||
result
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if review_result.reviews.is_empty() {
|
||||
return String::from("No issues found. ✅");
|
||||
}
|
||||
|
||||
let mut md = String::from("## Review Feedback\n\n");
|
||||
for (i, item) in review_result.reviews.iter().enumerate() {
|
||||
if i > 0 {
|
||||
md.push_str("\n---\n\n");
|
||||
}
|
||||
md.push_str(&format!("### `{}`\n\n", item.filename));
|
||||
if let Some(line) = item.line {
|
||||
md.push_str(&format!("> **Line {}**\n\n", line));
|
||||
}
|
||||
if !item.code.is_empty() {
|
||||
let lang = lang_from_filename(&item.filename);
|
||||
md.push_str(&format!("```{}\n{}\n```\n\n", lang, item.code));
|
||||
}
|
||||
md.push_str(&item.message);
|
||||
md.push('\n');
|
||||
}
|
||||
|
||||
if !review_result.comment.is_empty() {
|
||||
md.push_str("\n---\n\n");
|
||||
md.push_str("### Summary\n\n");
|
||||
md.push_str(&review_result.comment);
|
||||
md.push('\n');
|
||||
}
|
||||
|
||||
md
|
||||
}
|
||||
|
||||
async fn download_git_diff(&self, url: &str) -> anyhow::Result<String> {
|
||||
let response = reqwest::get(url).await?;
|
||||
let body = response.text().await?;
|
||||
Ok(body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
pub const GITEA_SIG_HEADER_NAME: &str = "x-gitea-signature";
|
||||
pub const GITEA_EVENT_TYPE_HEADER_NAME: &str = "x-gitea-event-type";
|
||||
pub const MAX_WEBHOOK_BODY_SIZE: usize = 1024 * 1024; // 1 MiB
|
||||
|
||||
pub const BOT_PROCESS_MSG: &str = "
|
||||
Review in progress with the model \"{model}\"...
|
||||
";
|
||||
|
||||
pub const REVIEW_PROMPT: &str = "
|
||||
You are a senior software engineer reviewing code changes.
|
||||
|
||||
Check good practices and code quality.
|
||||
|
||||
This is the pull request subject: \"{subject}\"
|
||||
|
||||
This is the user comment: \"{comment}\"
|
||||
|
||||
This is the git diff of the code changes:
|
||||
|
||||
\"{diff}\"
|
||||
|
||||
Please review the code changes and provide feedback.
|
||||
Return your feedback with only this json format, reviews must contain each review (filename field must contain the full path with extension) and comment msut contain a final summary:
|
||||
|
||||
{
|
||||
\"reviews\": [
|
||||
{
|
||||
\"filename\": \"\",
|
||||
\"line\": ,
|
||||
\"code\": \"\",
|
||||
\"message\": \"\"
|
||||
}
|
||||
],
|
||||
\"comment\": \"\"
|
||||
}
|
||||
";
|
||||
|
||||
@@ -6,6 +6,7 @@ pub struct EnvConfig {
|
||||
pub http_port: u16,
|
||||
pub webhook_secret: String,
|
||||
pub open_router_api_key: String,
|
||||
pub open_router_model: String,
|
||||
pub bot_name: String,
|
||||
pub gitea_url: String,
|
||||
pub gitea_token: String,
|
||||
@@ -18,6 +19,7 @@ pub fn load_config() -> anyhow::Result<EnvConfig> {
|
||||
let bot_name = try_get_env("BOT_NAME")?;
|
||||
let webhook_secret = try_get_env("WEBHOOK_SIG_HEADER_SECRET")?;
|
||||
let open_router_api_key = try_get_env("OPEN_ROUTER_API_KEY")?;
|
||||
let open_router_model = try_get_env("OPEN_ROUTER_MODEL")?;
|
||||
let gitea_url = try_get_env("GITEA_URL")?;
|
||||
let gitea_token = try_get_env("GITEA_TOKEN")?;
|
||||
|
||||
@@ -26,6 +28,7 @@ pub fn load_config() -> anyhow::Result<EnvConfig> {
|
||||
webhook_secret,
|
||||
bot_name,
|
||||
open_router_api_key,
|
||||
open_router_model,
|
||||
gitea_url,
|
||||
gitea_token,
|
||||
})
|
||||
|
||||
+15
-8
@@ -16,7 +16,12 @@ impl GiteaAPI {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn comment(&self, full_name: &str, index: u64) -> anyhow::Result<Comment> {
|
||||
pub async fn comment(
|
||||
&self,
|
||||
body: &str,
|
||||
full_name: &str,
|
||||
index: u64,
|
||||
) -> anyhow::Result<Comment> {
|
||||
let url = format!(
|
||||
"{}/api/v1/repos/{}/issues/{}/comments?access_token={}",
|
||||
self.base_url, full_name, index, self.token
|
||||
@@ -26,17 +31,20 @@ impl GiteaAPI {
|
||||
let res = client
|
||||
.post(url)
|
||||
.json(&json!({
|
||||
"body": "Hello world :)"
|
||||
"body": body
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
println!("{}", res.status());
|
||||
|
||||
res.json::<Comment>().await.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
pub async fn edit_comment(&self, full_name: &str, comment_id: u64) -> anyhow::Result<()> {
|
||||
pub async fn edit_comment(
|
||||
&self,
|
||||
body: &str,
|
||||
full_name: &str,
|
||||
comment_id: u64,
|
||||
) -> anyhow::Result<()> {
|
||||
let url = format!(
|
||||
"{}/api/v1/repos/{}/issues/comments/{}?access_token={}",
|
||||
self.base_url, full_name, comment_id, self.token
|
||||
@@ -46,13 +54,11 @@ impl GiteaAPI {
|
||||
let res = client
|
||||
.patch(url)
|
||||
.json(&json!({
|
||||
"body": "Updated Hello world :)"
|
||||
"body": body
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
println!("{}", res.status());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -75,6 +81,7 @@ pub struct PullRequest {
|
||||
pub id: u64,
|
||||
pub diff_url: String,
|
||||
pub number: u64,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
||||
+3
-5
@@ -6,19 +6,17 @@ mod consts;
|
||||
mod env;
|
||||
mod errors;
|
||||
mod gitea;
|
||||
mod open_router;
|
||||
mod state;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let config = env::load_config()?;
|
||||
let bot = Bot::new(config.clone());
|
||||
let bot = Bot::new(config.clone())?;
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<WebhookType>(1);
|
||||
|
||||
let app_state = AppState {
|
||||
bot_tx: tx,
|
||||
config,
|
||||
};
|
||||
let app_state = AppState { bot_tx: tx, config };
|
||||
|
||||
tokio::try_join!(bot.start(rx), api::start(app_state))?;
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
use openrouter_rs::{Message, api::chat::ChatCompletionRequest};
|
||||
|
||||
pub struct OpenRouterClient {
|
||||
client: openrouter_rs::OpenRouterClient,
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl OpenRouterClient {
|
||||
pub fn new(token: &str, model: &str) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
client: openrouter_rs::OpenRouterClient::builder()
|
||||
.api_key(token)
|
||||
.build()?,
|
||||
model: String::from(model),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn chat(&self, msg: &str) -> anyhow::Result<String> {
|
||||
let request = ChatCompletionRequest::builder()
|
||||
.model(&self.model)
|
||||
.messages(vec![Message::new(
|
||||
openrouter_rs::types::Role::Developer,
|
||||
msg,
|
||||
)])
|
||||
.build()?;
|
||||
|
||||
let response = self.client.chat().create(&request).await?;
|
||||
|
||||
response.choices[0]
|
||||
.content()
|
||||
.map(|msg| String::from(msg))
|
||||
.ok_or(anyhow::anyhow!("No content"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user