diff --git a/Cargo.lock b/Cargo.lock index 0ecff4e..7d80d64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,6 +89,29 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.9" @@ -97,6 +120,7 @@ checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "axum-macros", + "base64", "bytes", "form_urlencoded", "futures-util", @@ -115,8 +139,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1 0.10.7", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -217,6 +243,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -226,6 +254,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chacha20" version = "0.10.1" @@ -234,7 +268,7 @@ checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core", + "rand_core 0.10.1", ] [[package]] @@ -251,12 +285,31 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "cmov" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compression-codecs" version = "0.4.38" @@ -280,6 +333,26 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -405,6 +478,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "derive_arbitrary" version = "1.4.2" @@ -455,6 +534,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.17.0" @@ -464,6 +549,15 @@ dependencies = [ "serde", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -477,7 +571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -487,7 +581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -542,6 +636,12 @@ dependencies = [ "spin", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.2.0" @@ -557,6 +657,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.34" @@ -638,6 +744,31 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -645,9 +776,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi", - "rand_core", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -770,6 +922,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -778,6 +931,22 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] @@ -786,13 +955,23 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", + "system-configuration", "tokio", "tower-service", + "tracing", + "windows-registry", ] [[package]] @@ -947,12 +1126,77 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.104" @@ -1007,6 +1251,12 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "matchers" version = "0.2.0" @@ -1072,7 +1322,7 @@ checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1081,7 +1331,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1099,6 +1349,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "parking" version = "2.2.1" @@ -1184,6 +1440,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro-error-attr3" version = "3.1.0" @@ -1228,6 +1493,63 @@ dependencies = [ "yansi", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -1237,12 +1559,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.2" @@ -1250,8 +1588,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", - "getrandom", - "rand_core", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -1260,6 +1617,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1298,6 +1664,60 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rust-embed" version = "8.12.0" @@ -1333,6 +1753,96 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1354,12 +1864,50 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.229" @@ -1446,6 +1994,8 @@ dependencies = [ "figment", "http", "http-body-util", + "log", + "reqwest", "serde", "serde_json", "sqlx", @@ -1462,6 +2012,17 @@ dependencies = [ "validator", ] +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha1" version = "0.11.0" @@ -1526,6 +2087,22 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -1548,7 +2125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1668,7 +2245,7 @@ dependencies = [ "log", "percent-encoding", "serde", - "sha1", + "sha1 0.11.0", "sha2 0.11.0", "sqlx-core", "thiserror", @@ -1700,7 +2277,7 @@ dependencies = [ "log", "md-5", "memchr", - "rand", + "rand 0.10.2", "serde", "serde_json", "sha2 0.11.0", @@ -1762,6 +2339,12 @@ 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 = "2.0.119" @@ -1789,6 +2372,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -1801,6 +2387,27 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "thiserror" version = "2.0.20" @@ -1868,7 +2475,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1882,6 +2489,16 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.19" @@ -1893,6 +2510,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -1902,6 +2531,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -1973,6 +2603,7 @@ dependencies = [ "bitflags", "bytes", "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -1982,6 +2613,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "url", "uuid", ] @@ -2072,6 +2704,28 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1 0.10.7", + "thiserror", +] + [[package]] name = "typenum" version = "1.20.1" @@ -2120,6 +2774,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -2200,7 +2860,7 @@ version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" dependencies = [ - "getrandom", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -2263,12 +2923,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -2282,6 +2960,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.127" @@ -2314,6 +3002,35 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "2.1.3" @@ -2326,7 +3043,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2370,6 +3087,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -2388,6 +3116,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2397,6 +3134,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.7.15" @@ -2406,6 +3207,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.4" @@ -2441,6 +3248,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "zerofrom" version = "0.1.8" @@ -2462,6 +3289,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zerotrie" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 414af0a..e3b2da8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://ikibani.com/kashiro/rust-template" [workspace.dependencies] anyhow = "1" -axum = { version = "0.8", features = ["json", "macros", "tokio"] } +axum = { version = "0.8", features = ["json", "macros", "tokio", "ws"] } chrono = { version = "0.4", features = ["serde"] } dotenvy = "0.15" figment = { version = "0.10", features = ["toml", "env"] } diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml index ab8e91e..9da0d38 100644 --- a/apps/server/Cargo.toml +++ b/apps/server/Cargo.toml @@ -21,6 +21,7 @@ chrono.workspace = true dotenvy.workspace = true figment.workspace = true http.workspace = true +reqwest = { version = "0.13.4", features = ["json"] } serde.workspace = true serde_json.workspace = true sqlx.workspace = true @@ -35,6 +36,7 @@ utoipa-axum.workspace = true utoipa-swagger-ui.workspace = true uuid.workspace = true validator.workspace = true +log = "0.4.33" [dev-dependencies] http-body-util = "0.1" diff --git a/apps/server/migrations/0002_queries.sql b/apps/server/migrations/0002_queries.sql new file mode 100644 index 0000000..31eb804 --- /dev/null +++ b/apps/server/migrations/0002_queries.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS queries ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS query_messages ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + query_id UUID REFERENCES queries(id), + message TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); \ No newline at end of file diff --git a/apps/server/src/config.rs b/apps/server/src/config.rs index 0c489ff..9b36628 100644 --- a/apps/server/src/config.rs +++ b/apps/server/src/config.rs @@ -74,7 +74,7 @@ impl Default for AppConfig { fn default() -> Self { Self { app: AppSection { - name: "rust-template".into(), + name: "Lyra".into(), description: "Axum + Vue + PostgreSQL fullstack app".into(), }, server: ServerSection { diff --git a/apps/server/src/error.rs b/apps/server/src/error.rs index 327ab9a..b6ca9ea 100644 --- a/apps/server/src/error.rs +++ b/apps/server/src/error.rs @@ -35,6 +35,12 @@ pub enum ApiError { Internal(#[from] anyhow::Error), } +impl From for ApiError { + fn from(err: reqwest::Error) -> Self { + ApiError::Internal(anyhow::anyhow!(err)) + } +} + impl ApiError { pub fn status(&self) -> StatusCode { match self { diff --git a/apps/server/src/handlers/llm.rs b/apps/server/src/handlers/llm.rs new file mode 100644 index 0000000..df48ce2 --- /dev/null +++ b/apps/server/src/handlers/llm.rs @@ -0,0 +1,198 @@ +use crate::models::llm::{LlmQuery, LlmRequest, LlmWsEvent, SavedQueriesResponse}; +use crate::AppState; +use axum::extract::State; + +use axum::{extract::{ + ws::{Message, WebSocket}, + WebSocketUpgrade, +}, response::IntoResponse}; +use sqlx::Row; +use uuid::Uuid; +use crate::error::ApiError; + +#[utoipa::path( + get, + path = "/api/v1/llm/ws", + tag = "llm", + description = "WebSocket upgrade. After 101, send LlmRequest as one text frame. Server sends LlmWsEvent frames.", + responses((status = 101, description = "Switching Protocols")) +)] +pub async fn send( + ws: WebSocketUpgrade, + State(state): State, +) -> impl IntoResponse { + ws.on_upgrade(move |socket| handle_send(socket, state)) +} + +async fn handle_send(mut socket: WebSocket, state: AppState) { + let mut full_body = String::new(); + + let Some(Ok(Message::Text(text))) = socket.recv().await else { + return; + }; + + let mut body: LlmRequest = match serde_json::from_str(&text) { + Ok(v) => v, + Err(e) => { + let _ = send_event( + &mut socket, + &LlmWsEvent::Error { + message: e.to_string(), + }, + ) + .await; + return; + } + }; + body.stream = Some(true); + + let mut resp = match state + .0 + .client + .post("http://127.0.0.1:11434/api/chat") + .json(&body) + .send() + .await + { + Ok(r) => r, + Err(e) => { + let _ = send_event( + &mut socket, + &LlmWsEvent::Error { + message: e.to_string(), + }, + ) + .await; + return; + } + }; + + if !resp.status().is_success() { + let status = resp.status(); + let text = resp.text().await.unwrap_or_default(); + let _ = send_event( + &mut socket, + &LlmWsEvent::Error { + message: format!("{status}: {text}"), + }, + ) + .await; + return; + } + + let mut buf = String::new(); + loop { + let chunk = match resp.chunk().await { + Ok(Some(bytes)) => bytes, + Ok(None) => break, + Err(e) => { + let _ = send_event( + &mut socket, + &LlmWsEvent::Error { + message: e.to_string(), + }, + ) + .await; + return; + } + }; + + buf.push_str(&String::from_utf8_lossy(&chunk)); + while let Some(i) = buf.find('\n') { + let line = buf[..i].trim().to_string(); + buf.drain(..=i); + if line.is_empty() { + continue; + } + + let event: serde_json::Value = match serde_json::from_str(&line) { + Ok(v) => v, + Err(_) => continue, + }; + + if let Some(err) = event.get("error").and_then(|v| v.as_str()) { + let _ = send_event( + &mut socket, + &LlmWsEvent::Error { + message: err.to_string(), + }, + ) + .await; + return; + } + + if let Some(piece) = event + .pointer("/message/content") + .and_then(|v| v.as_str()) + .filter(|s| !s.is_empty()) + { + full_body.push_str(piece); + if send_event( + &mut socket, + &LlmWsEvent::Delta { + content: piece.to_string(), + }, + ) + .await + .is_err() + { + return; + } + } + + if event.get("done").and_then(|v| v.as_bool()) == Some(true) { + let reason = event + .get("done_reason") + .and_then(|v| v.as_str()) + .unwrap_or("stop") + .to_string(); + + let title = body + .messages + .last() + .map(|m| m.content.as_str()) + .unwrap_or("New Query"); + + if let Err(err) = save_query(&state, title, &full_body).await { + let _ = send_event( + &mut socket, + &LlmWsEvent::Error { message: err.to_string() }, + ) + .await; + return; + } + + let _ = send_event(&mut socket, &LlmWsEvent::Done { reason }).await; + return; + } + } + } +} + +async fn send_event( + socket: &mut WebSocket, + event: &LlmWsEvent, +) -> Result<(), axum::Error> { + socket + .send(Message::text(serde_json::to_string(event).unwrap())) + .await +} + +async fn save_query(state: &AppState, title: &str, body: &str) -> Result { + let result = sqlx::query("insert into queries (title) values ($1) returning id") + .bind(title) + .fetch_one(&state.db) + .await + .map_err(|_| ApiError::Internal(anyhow::Error::msg("Failed to save query")))?; + + let query_id: Uuid = result.get("id"); + + sqlx::query("insert into query_messages (query_id, message) values ($1, $2)") + .bind(query_id) + .bind(body) + .execute(&state.db) + .await + .map_err(|_| ApiError::Internal(anyhow::Error::msg("Failed to save query")))?; + + Ok(query_id) +} \ No newline at end of file diff --git a/apps/server/src/handlers/mod.rs b/apps/server/src/handlers/mod.rs index b3c45be..c8f79c4 100644 --- a/apps/server/src/handlers/mod.rs +++ b/apps/server/src/handlers/mod.rs @@ -1,2 +1,4 @@ pub mod health; pub mod items; +pub mod llm; +pub mod queries; diff --git a/apps/server/src/handlers/queries.rs b/apps/server/src/handlers/queries.rs new file mode 100644 index 0000000..93f25ac --- /dev/null +++ b/apps/server/src/handlers/queries.rs @@ -0,0 +1,61 @@ +use axum::extract::{Path, Query, State}; +use axum::Json; +use uuid::Uuid; +use crate::AppState; +use crate::error::{ApiError, ApiResult}; +use crate::models::llm::{LlmQuery, LlmQueryDetail, SavedQueriesRequest, SavedQueriesResponse}; + +#[utoipa::path( + get, + path = "/api/v1/queries/list", + tag = "queries", + description = "Get all saved queries", + params( + ("limit" = Option, Query, description = "Maximum number of queries to return") + ), + responses( + (status = 200, description = "Fetched LLM queries", body = SavedQueriesResponse), + (status = 400, description = "Validation error", body = crate::error::ErrorBody) + ) +)] +pub async fn get_queries(State(state): State, Query(params): Query) -> ApiResult> { + let limit = params.limit.unwrap_or(50); + let result = sqlx::query_as::<_, LlmQuery>("select id, title from queries order by id desc limit $1") + .bind(limit) + .fetch_all(&state.db) + .await + .map_err(|_| ApiError::Internal(anyhow::Error::msg("Failed to get queries")))?; + + Ok(Json(SavedQueriesResponse { queries: result })) +} + +#[utoipa::path( + get, + path = "/api/v1/queries/{id}", + tag = "queries", + description = "Get a single saved query by ID", + params(("id" = Uuid,)), + responses( + (status = 200, description = "Fetched LLM query", body = LlmQueryDetail), + (status = 404, description = "Query not found", body = crate::error::ErrorBody), + (status = 400, description = "Validation error", body = crate::error::ErrorBody) + ) +)] +pub async fn get_query(State(state): State, Path(id): Path) -> ApiResult> { + let query = sqlx::query!("select id, title from queries where id = $1", id) + .fetch_optional(&state.db) + .await + .map_err(|_| ApiError::Internal(anyhow::Error::msg("Failed to fetch query")))? + .ok_or_else(|| ApiError::NotFound(format!("Query {} not found", id)))?; + + let messages = sqlx::query!("select message from query_messages where query_id = $1 order by id asc", id) + .fetch_all(&state.db) + .await + .map_err(|_| ApiError::Internal(anyhow::Error::msg("Failed to fetch query messages")))?; + + Ok(Json(LlmQueryDetail { + id: query.id, + title: query.title, + messages: messages.into_iter().map(|m| m.message).collect(), + })) +} \ No newline at end of file diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 73fa843..8e5e9e1 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -29,11 +29,18 @@ async fn main() -> anyhow::Result<()> { tracing::info!("migrations applied"); } - // 4. Router + // 4. HTTP client + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(480)) + .build()?; + + let state = AppState::new(config.clone(), pool, client); + + // 5. Router let addr = config.socket_addr()?; - let app = routes::router(AppState::new(config, pool)); + let app = routes::router(state); - // 5. Serve + // 6. Serve let listener = tokio::net::TcpListener::bind(addr).await?; tracing::info!(%addr, "listening"); axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()).await?; diff --git a/apps/server/src/models/llm.rs b/apps/server/src/models/llm.rs new file mode 100644 index 0000000..776cca7 --- /dev/null +++ b/apps/server/src/models/llm.rs @@ -0,0 +1,107 @@ +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; +use uuid::Uuid; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct LlmResponse { + pub model: String, + #[serde(rename = "created_at")] + pub created_at: String, + pub message: ResponseMessage, + pub done: bool, + #[serde(rename = "done_reason")] + pub done_reason: String, + #[serde(rename = "total_duration")] + pub total_duration: i64, + #[serde(rename = "load_duration")] + pub load_duration: i64, + #[serde(rename = "prompt_eval_count")] + pub prompt_eval_count: i64, + #[serde(rename = "prompt_eval_duration")] + pub prompt_eval_duration: i64, + #[serde(rename = "eval_count")] + pub eval_count: i64, + #[serde(rename = "eval_duration")] + pub eval_duration: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct ResponseMessage { + pub role: String, + pub content: String, + pub thinking: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct LlmRequest { + pub model: String, + pub messages: Vec, + pub stream: Option, + pub options: Options, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct RequestMessage { + pub role: String, + pub content: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct Options { + pub temperature: f64, + #[serde(rename = "num_predict")] + pub num_predict: Option, +} + +#[derive(serde::Serialize, utoipa::ToSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum LlmWsEvent { + Delta { content: String }, + Done { reason: String }, + Error { message: String }, +} + +#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub struct SaveQueryRequest { + pub title: String, + pub body: String, +} + +#[derive(Serialize, Deserialize, utoipa::ToSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub struct SaveQueryResponse { + pub id: Uuid, +} + +#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub struct SavedQueriesRequest { + pub limit: Option, +} + +#[derive(Serialize, Deserialize, utoipa::ToSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub struct SavedQueriesResponse { + pub queries: Vec, +} + +#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema, FromRow)] +#[serde(tag = "type", rename_all = "snake_case")] +pub struct LlmQuery { + pub id: Uuid, + pub title: String, +} + +#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub struct LlmQueryDetail { + pub id: Uuid, + pub title: String, + pub messages: Vec, +} diff --git a/apps/server/src/models/mod.rs b/apps/server/src/models/mod.rs index fa15298..28c5bed 100644 --- a/apps/server/src/models/mod.rs +++ b/apps/server/src/models/mod.rs @@ -1,3 +1,4 @@ pub mod health; pub mod item; pub mod pagination; +pub mod llm; diff --git a/apps/server/src/routes/mod.rs b/apps/server/src/routes/mod.rs index 1635cbf..309505b 100644 --- a/apps/server/src/routes/mod.rs +++ b/apps/server/src/routes/mod.rs @@ -1,6 +1,8 @@ +use crate::routes::llm::__path_send; use axum::Router; use axum::http::{HeaderValue, Method, header}; use axum::middleware; +use axum::routing::get; use tower::ServiceBuilder; use tower_http::compression::CompressionLayer; use tower_http::cors::{AllowOrigin, CorsLayer}; @@ -12,7 +14,8 @@ use utoipa_axum::routes; use utoipa_swagger_ui::SwaggerUi; use crate::config::AppConfig; -use crate::handlers::{health, items}; +use crate::handlers::{health, items, llm, queries}; +use crate::handlers::llm::send; use crate::middleware::request_log::{REQUEST_ID_HEADER, request_logging}; use crate::state::AppState; @@ -47,6 +50,9 @@ pub fn openapi_router() -> OpenApiRouter { .routes(routes!(items::create_item)) .routes(routes!(items::update_item)) .routes(routes!(items::delete_item)) + .routes(routes!(send)) + .routes(routes!(queries::get_queries)) + .routes(routes!(queries::get_query)) } pub fn openapi_spec() -> utoipa::openapi::OpenApi { diff --git a/apps/server/src/state.rs b/apps/server/src/state.rs index 7903071..6dc8062 100644 --- a/apps/server/src/state.rs +++ b/apps/server/src/state.rs @@ -6,16 +6,17 @@ use sqlx::PgPool; use crate::config::AppConfig; #[derive(Clone)] -pub struct AppState(Arc); +pub struct AppState(pub(crate) Arc); pub struct InnerState { pub config: AppConfig, pub db: PgPool, + pub client: reqwest::Client, } impl AppState { - pub fn new(config: AppConfig, db: PgPool) -> Self { - Self(Arc::new(InnerState { config, db })) + pub fn new(config: AppConfig, db: PgPool, client: reqwest::Client) -> Self { + Self(Arc::new(InnerState { config, db, client })) } pub fn db(&self) -> &PgPool { diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index 022f83b..1534430 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -8,10 +8,15 @@ "name": "web", "version": "0.1.0", "dependencies": { - "vue": "^3.5.18" + "dompurify": "^3.0.1", + "marked": "^4.3.0", + "pinia": "^2.1.5", + "vue": "^3.5.18", + "vue-router": "^4.2.2" }, "devDependencies": { "@tailwindcss/vite": "^4.1.11", + "@types/marked": "^4.3.0", "@types/node": "^22.17.0", "@vitejs/plugin-vue": "^6.0.1", "smol-toml": "^1.4.2", @@ -1211,6 +1216,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/marked": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz", + "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.20.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", @@ -1221,6 +1233,13 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", @@ -1317,6 +1336,12 @@ "@vue/shared": "3.5.41" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, "node_modules/@vue/language-core": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.10.tgz", @@ -1404,6 +1429,15 @@ "node": ">=8" } }, + "node_modules/dompurify": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/enhanced-resolve": { "version": "5.24.5", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", @@ -1798,6 +1832,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/muggle-string": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", @@ -1849,6 +1895,28 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/postcss": { "version": "8.5.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", @@ -2107,6 +2175,47 @@ } } }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/vue-tsc": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.10.tgz", diff --git a/apps/web/package.json b/apps/web/package.json index 8cc4c47..0e91168 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,10 +11,15 @@ "generate:api": "node ../../scripts/generate-api.mjs" }, "dependencies": { - "vue": "^3.5.18" + "vue": "^3.5.18", + "vue-router": "^4.2.2", + "pinia": "^2.1.5", + "marked": "^4.3.0", + "dompurify": "^3.0.1" }, "devDependencies": { "@tailwindcss/vite": "^4.1.11", + "@types/marked": "^4.3.0", "@types/node": "^22.17.0", "@vitejs/plugin-vue": "^6.0.1", "smol-toml": "^1.4.2", diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue index 3bebf1c..d2a84fe 100644 --- a/apps/web/src/App.vue +++ b/apps/web/src/App.vue @@ -1,30 +1,31 @@ diff --git a/apps/web/src/api/generated.ts b/apps/web/src/api/generated.ts index 94798e0..a1b37f9 100644 --- a/apps/web/src/api/generated.ts +++ b/apps/web/src/api/generated.ts @@ -38,11 +38,26 @@ export type ItemResponse = { updated_at: string; }; +export type LlmQuery = { + id: string; + title: string; +}; + +export type LlmQueryDetail = { + id: string; + messages: Array; + title: string; +}; + export type ReadyResponse = { database: string; status: string; }; +export type SavedQueriesResponse = { + queries: Array; +}; + export type UpdateItemRequest = { description?: string | null; name?: string | null; @@ -111,6 +126,39 @@ export function updateItem(args: { }); } +export function send(): Promise { + return request({ + method: "GET", + path: "/api/v1/llm/ws", + expectedStatus: 200, + }); +} + +export function getQueries(args: { + query?: { + limit?: number; + } +}): Promise { + return request({ + method: "GET", + path: "/api/v1/queries/list", + query: args.query, + expectedStatus: 200, + }); +} + +export function getQuery(args: { + path: { + id: string; + } +}): Promise { + return request({ + method: "GET", + path: "/api/v1/queries/" + encodeURIComponent(String(args.path.id)), + expectedStatus: 200, + }); +} + export function live(): Promise { return request({ method: "GET", diff --git a/apps/web/src/components/QueryBox.vue b/apps/web/src/components/QueryBox.vue new file mode 100644 index 0000000..40f81ec --- /dev/null +++ b/apps/web/src/components/QueryBox.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web/src/components/QueryList.vue b/apps/web/src/components/QueryList.vue new file mode 100644 index 0000000..0e99e67 --- /dev/null +++ b/apps/web/src/components/QueryList.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index f4b9bf0..e36576b 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -1,6 +1,15 @@ import { createApp } from "vue"; +import { createMemoryHistory, createRouter } from 'vue-router'; +import {routes} from './routes'; -import App from "./App.vue"; import "./style.css"; +import { createPinia } from 'pinia'; +import App from "./App.vue"; + +const pinia = createPinia(); +export const router = createRouter({ + history: createMemoryHistory(), + routes, +}) -createApp(App).mount("#app"); +createApp(App).use(router).use(pinia).mount("#app"); diff --git a/apps/web/src/routes/index.ts b/apps/web/src/routes/index.ts new file mode 100644 index 0000000..dd5eb74 --- /dev/null +++ b/apps/web/src/routes/index.ts @@ -0,0 +1,8 @@ +import HomeView from "@/views/HomeView.vue"; +import ResponseView from "@/views/ResponseView.vue"; + +export const routes = [ + { path: '/', component: HomeView }, + { path: '/response', component: ResponseView }, + { path: '/response/:id', component: ResponseView }, +] diff --git a/apps/web/src/stores/prompt.ts b/apps/web/src/stores/prompt.ts new file mode 100644 index 0000000..d082274 --- /dev/null +++ b/apps/web/src/stores/prompt.ts @@ -0,0 +1,18 @@ +import { defineStore } from 'pinia'; + +export const usePromptStore = defineStore('prompt', { + state: () => ({ + prompt: '' + }), + actions: { + setPrompt(prompt: string) { + this.prompt = prompt; + }, + getPrompt() { + return this.prompt; + }, + clearPrompt() { + this.prompt = ''; + } + } +}); \ No newline at end of file diff --git a/apps/web/src/views/HomeView.vue b/apps/web/src/views/HomeView.vue new file mode 100644 index 0000000..a966a74 --- /dev/null +++ b/apps/web/src/views/HomeView.vue @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/apps/web/src/views/ResponseView.vue b/apps/web/src/views/ResponseView.vue new file mode 100644 index 0000000..991656a --- /dev/null +++ b/apps/web/src/views/ResponseView.vue @@ -0,0 +1,273 @@ + + + + + \ No newline at end of file diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 7f440bb..bdd8296 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -110,7 +110,7 @@ export default defineConfig({ port: frontendPort, strictPort: true, proxy: { - "/api": { target: backendOrigin, changeOrigin: true }, + "/api": { target: backendOrigin, changeOrigin: true, ws: true }, "/health": { target: backendOrigin, changeOrigin: true }, }, }, diff --git a/packages/contracts/openapi.json b/packages/contracts/openapi.json index d4c8896..21eb992 100644 --- a/packages/contracts/openapi.json +++ b/packages/contracts/openapi.json @@ -222,6 +222,115 @@ } } }, + "/api/v1/llm/ws": { + "get": { + "tags": [ + "llm" + ], + "description": "WebSocket upgrade. After 101, send LlmRequest as one text frame. Server sends LlmWsEvent frames.", + "operationId": "send", + "responses": { + "101": { + "description": "Switching Protocols" + } + } + } + }, + "/api/v1/queries/list": { + "get": { + "tags": [ + "queries" + ], + "description": "Get all saved queries", + "operationId": "get_queries", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Maximum number of queries to return", + "required": false, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Fetched LLM queries", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SavedQueriesResponse" + } + } + } + }, + "400": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorBody" + } + } + } + } + } + } + }, + "/api/v1/queries/{id}": { + "get": { + "tags": [ + "queries" + ], + "description": "Get a single saved query by ID", + "operationId": "get_query", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Fetched LLM query", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LlmQueryDetail" + } + } + } + }, + "400": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorBody" + } + } + } + }, + "404": { + "description": "Query not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorBody" + } + } + } + } + } + } + }, "/health/live": { "get": { "tags": [ @@ -411,6 +520,45 @@ } } }, + "LlmQuery": { + "type": "object", + "required": [ + "id", + "title" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + } + } + }, + "LlmQueryDetail": { + "type": "object", + "required": [ + "id", + "title", + "messages" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "messages": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string" + } + } + }, "ReadyResponse": { "type": "object", "required": [ @@ -426,6 +574,20 @@ } } }, + "SavedQueriesResponse": { + "type": "object", + "required": [ + "queries" + ], + "properties": { + "queries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LlmQuery" + } + } + } + }, "UpdateItemRequest": { "type": "object", "properties": { diff --git a/scripts/generate-api.mjs b/scripts/generate-api.mjs index 4cd06f3..0d811fa 100644 --- a/scripts/generate-api.mjs +++ b/scripts/generate-api.mjs @@ -131,10 +131,10 @@ function operationName(method, pathName, operation, used) { return uniqueName(camel(`${method}_${slug}`), used); } -function pathToTemplate(pathName, paramNames) { +function pathToTemplate(pathName, paramNames, argPrefix) { let template = JSON.stringify(pathName); for (const name of paramNames) { - template = template.replace(`{${name}}`, `" + encodeURIComponent(String(args.path.${name})) + "`); + template = template.replace(`{${name}}`, `" + encodeURIComponent(String(${argPrefix}.path.${name})) + "`); } return template.replace(/ \+ ""/g, ""); } @@ -191,14 +191,16 @@ for (const [pathName, item] of Object.entries(paths)) { args.push(`body: ${tsType(bodySchema, spec)}`); } - const argList = args.length ? `args: {\n ${args.join(";\n ")}\n}` : ""; + const argPrefix = (pathParams.length || queryParams.length || bodySchema) ? "args" : "_args"; + const argList = args.length ? `${argPrefix}: {\n ${args.join(";\n ")}\n}` : ""; const returnType = responseSchema ? tsType(responseSchema, spec) : "void"; const pathExpr = pathToTemplate( pathName, pathParams.map((param) => param.name), + argPrefix, ); - const queryLine = queryParams.length ? " query: args.query," : ""; - const bodyLine = bodySchema ? " body: args.body," : ""; + const queryLine = queryParams.length ? ` query: ${argPrefix}.query,` : ""; + const bodyLine = bodySchema ? ` body: ${argPrefix}.body,` : ""; const requestFields = [ ` method: "${method.toUpperCase()}",`,