diff --git a/Cargo.lock b/Cargo.lock index a63ea2f..09eceeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,11 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" @@ -31,9 +22,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -49,9 +40,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -64,37 +55,54 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "once_cell", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -105,23 +113,63 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "backtrace" -version = "0.3.74" +name = "axum" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", ] [[package]] @@ -138,15 +186,15 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -156,24 +204,25 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.15" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" @@ -190,9 +239,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.30" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -200,9 +249,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -212,9 +261,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -224,15 +273,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" @@ -252,7 +301,7 @@ checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "windows-sys 0.61.2", ] @@ -295,6 +344,16 @@ dependencies = [ "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" @@ -337,21 +396,21 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -402,7 +461,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", + "redox_users 0.5.2", "windows-sys 0.61.2", ] @@ -458,29 +517,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -489,12 +525,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -511,9 +547,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" @@ -531,6 +573,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -548,18 +596,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -572,9 +620,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -582,15 +630,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -605,9 +653,9 @@ checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -616,21 +664,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -640,44 +688,50 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "r-efi 5.3.0", + "wasip2", ] [[package]] -name = "gimli" -version = "0.31.1" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] [[package]] name = "h2" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -703,9 +757,18 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hashlink" @@ -722,12 +785,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -752,7 +809,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.18", "tokio", "ureq", "windows-sys 0.61.2", @@ -760,12 +817,11 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -781,12 +837,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -794,29 +850,31 @@ dependencies = [ [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "humantime" -version = "2.1.0" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -826,16 +884,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -859,21 +915,27 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +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]] @@ -902,21 +964,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", + "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -925,104 +989,72 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] [[package]] -name = "icu_provider_macros" -version = "1.5.0" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1031,9 +1063,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1041,12 +1073,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.17.0", + "serde", + "serde_core", ] [[package]] @@ -1057,46 +1091,58 @@ checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" dependencies = [ "console", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "unit-prefix", "web-time", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "is-terminal" -version = "0.4.15" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1107,19 +1153,24 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.170" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags", "libc", ] @@ -1136,15 +1187,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -1154,25 +1205,39 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.26" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -1182,9 +1247,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -1192,20 +1257,20 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -1228,12 +1293,21 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.2.1" +name = "nu-ansi-term" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[package]] +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1248,30 +1322,27 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.5.2", + "hermit-abi", "libc", ] [[package]] -name = "object" -version = "0.36.7" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "once_cell" -version = "1.20.3" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags", "cfg-if", @@ -1295,15 +1366,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -1319,9 +1390,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1329,15 +1400,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -1351,27 +1422,41 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "pin-project" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pin-project-internal" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" @@ -1379,6 +1464,15 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1394,6 +1488,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "prettytable-rs" version = "0.10.0" @@ -1410,9 +1514,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1421,14 +1525,14 @@ dependencies = [ name = "puma" version = "0.0.2" dependencies = [ + "axum", "chrono", "clap", "colored", "dirs", - "env_logger", + "futures", "hf-hub", "indicatif", - "log", "prettytable-rs", "regex", "reqwest", @@ -1440,17 +1544,35 @@ dependencies = [ "sysinfo", "tempfile", "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-http 0.5.2", + "tracing", + "tracing-subscriber", + "uuid", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 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.4" @@ -1477,7 +1599,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.4", ] [[package]] @@ -1502,9 +1624,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -1515,27 +1637,27 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", "libredox", "thiserror 1.0.69", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.11", + "thiserror 2.0.18", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1545,9 +1667,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1556,15 +1678,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -1579,42 +1701,39 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-util", - "tower", + "tower 0.5.3", + "tower-http 0.6.8", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "windows-registry", ] [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1644,30 +1763,24 @@ dependencies = [ "rusqlite", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustix" -version = "0.38.44" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" dependencies = [ "log", "once_cell", @@ -1679,25 +1792,19 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ - "rustls-pki-types", + "zeroize", ] -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" - [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -1706,23 +1813,23 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1733,12 +1840,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1746,14 +1853,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +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.228" @@ -1786,14 +1899,26 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "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]] @@ -1808,6 +1933,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1816,10 +1950,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1831,27 +1966,24 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1867,9 +1999,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -1885,9 +2017,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1905,9 +2037,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -1930,12 +2062,12 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -1951,16 +2083,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.17.1" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1985,11 +2116,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.18", ] [[package]] @@ -2005,15 +2136,24 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.47" @@ -2047,9 +2187,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -2057,11 +2197,10 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ - "backtrace", "bytes", "libc", "mio", @@ -2070,14 +2209,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -2096,19 +2235,30 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2119,9 +2269,24 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -2130,6 +2295,42 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower 0.5.3", + "tower-layer", + "tower-service", ] [[package]] @@ -2146,21 +2347,64 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -2171,9 +2415,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" @@ -2183,9 +2427,15 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unit-prefix" @@ -2237,21 +2487,16 @@ dependencies = [ [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8-zero" version = "0.8.1" @@ -2270,6 +2515,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2293,63 +2556,56 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen 0.57.1", ] [[package]] -name = "wasm-bindgen" -version = "0.2.100" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "wit-bindgen 0.51.0", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" +name = "wasm-bindgen" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2357,26 +2613,48 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -2390,11 +2668,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -2482,7 +2772,7 @@ dependencies = [ "windows-interface 0.59.3", "windows-link", "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-strings", ] [[package]] @@ -2537,13 +2827,13 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets", + "windows-link", + "windows-result 0.4.1", + "windows-strings", ] [[package]] @@ -2555,15 +2845,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -2573,16 +2854,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets", -] - [[package]] name = "windows-strings" version = "0.5.1" @@ -2684,33 +2955,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wit-bindgen-rt" -version = "0.33.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", ] [[package]] -name = "write16" -version = "1.0.0" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -2718,9 +3067,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -2730,18 +3079,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -2750,18 +3099,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -2771,15 +3120,26 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -2788,11 +3148,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index e00701d..d01e472 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,8 @@ reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0" -env_logger = "0.11.6" -log = "0.4.26" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } indicatif = "0.18" dirs = "6.0.0" hf-hub = { version = "0.5.0", features = ["tokio"] } @@ -26,5 +26,15 @@ rusqlite = { version = "0.32", features = ["bundled"] } rusqlite_migration = "1.3" regex = "1.11" +# Web server +axum = "0.7" +tower = "0.4" +tower-http = { version = "0.5", features = ["cors", "trace"] } +uuid = { version = "1.0", features = ["v4", "serde"] } +futures = "0.3" +tokio-stream = "0.1" + [dev-dependencies] tempfile = "3.12" +tower = { version = "0.4", features = ["util"] } +serde_json = "1.0" diff --git a/README.md b/README.md index 2ec2821..4c05803 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ 💻 **System Detection** - Automatic GPU detection and resource reporting +🚀 **OpenAI-Compatible API** - RESTful API with streaming support + ## Installation ### Install with Cargo @@ -45,6 +47,8 @@ make build ## Quick Start +### CLI Usage + ```bash # Download a model puma pull inftyai/tiny-random-gpt2 @@ -62,6 +66,39 @@ puma info puma rm inftyai/tiny-random-gpt2 ``` +### API Server + +```bash +# Start the inference server +puma serve + +# Server will start on http://0.0.0.0:8000 +# API endpoints: +# POST /v1/chat/completions +# POST /v1/completions +# GET /v1/models +# GET /v1/models/:model +# GET /health +``` + +**Test the API:** + +```bash +# Health check +curl http://localhost:8000/health + +# Chat completion +curl http://localhost:8000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "inftyai/tiny-random-gpt2", + "messages": [{"role": "user", "content": "Hello!"}] + }' + +# Or use the test script +./hack/scripts/test_api.sh +``` + ## Commands | Command | Status | Description | @@ -72,6 +109,7 @@ puma rm inftyai/tiny-random-gpt2 | `rm ` | ✅ | Remove model and cache | | `info` | ✅ | Display system information | | `version` | ✅ | Show PUMA version | +| `serve` | ✅ | Start OpenAI-compatible API server | | `ps` | 🚧 | List running models | | `run` | 🚧 | Start model inference | | `stop` | 🚧 | Stop running model | @@ -106,6 +144,81 @@ puma ls llama -l author=meta **Available filters:** `author`, `task`, `license`, `provider`, `model_series` +## API Server + +PUMA provides an OpenAI-compatible API server for model inference. + +### Starting the Server + +```bash +# Default: 0.0.0.0:8000 +puma serve + +# Custom host and port +puma serve --host 127.0.0.1 --port 3000 +``` + +### API Endpoints + +#### Chat Completions (Recommended) +```bash +curl http://localhost:8000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "inftyai/tiny-random-gpt2", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"} + ], + "max_tokens": 100, + "temperature": 0.7 + }' +``` + +#### Streaming (Server-Sent Events) +```bash +curl http://localhost:8000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "inftyai/tiny-random-gpt2", + "messages": [{"role": "user", "content": "Tell me a story"}], + "stream": true + }' +``` + +#### List Models +```bash +curl http://localhost:8000/v1/models +``` + +#### Health Check +```bash +curl http://localhost:8000/health +# Returns: {"status":"ok","version":"0.0.2"} +``` + +### OpenAI Python Client + +PUMA is compatible with the OpenAI Python SDK: + +```python +from openai import OpenAI + +client = OpenAI( + base_url="http://localhost:8000/v1", + api_key="dummy" # Not required +) + +response = client.chat.completions.create( + model="inftyai/tiny-random-gpt2", + messages=[ + {"role": "user", "content": "Hello!"} + ] +) + +print(response.choices[0].message.content) +``` + ### Inspect Output ```bash @@ -146,8 +259,11 @@ Models are stored with lowercase names for case-insensitive matching. # Build make build -# Run tests (67 unit + 22 integration) +# Run all tests make test + +# Test API manually +./hack/scripts/test_api.sh ``` ### Project Structure @@ -155,13 +271,16 @@ make test ``` puma/ ├── src/ -│ ├── cli/ # Command implementations (ls, rm, inspect) +│ ├── api/ # OpenAI-compatible API +│ ├── backend/ # Inference backends (Mock, MLX) +│ ├── cli/ # Command implementations │ ├── downloader/ # HuggingFace download logic │ ├── registry/ # Model registry & metadata │ ├── storage/ # SQLite storage backend │ ├── system/ # System info detection │ └── utils/ # Formatting & helpers ├── tests/ # Integration tests +├── hack/ # Development scripts ├── Cargo.toml # Rust dependencies └── Makefile # Build commands ``` diff --git a/hack/README.md b/hack/README.md new file mode 100644 index 0000000..ca9895f --- /dev/null +++ b/hack/README.md @@ -0,0 +1,68 @@ +# Hack Directory + +Development and testing utilities for PUMA. + +## Structure + +``` +hack/ +└── scripts/ # Test and utility scripts + └── test_api.sh +``` + +## Scripts + +### `scripts/test_api.sh` + +Tests all PUMA API endpoints manually. + +**Usage:** +```bash +# Start PUMA server first +./puma serve + +# In another terminal +./hack/scripts/test_api.sh +``` + +**Tests:** +- Health check +- List models +- Chat completion (non-streaming) +- Chat completion (streaming) +- Text completion + +**Requirements:** +- Running PUMA server +- `curl` and `jq` installed + +--- + + +## Adding New Scripts + +Place development and testing scripts in `hack/scripts/`: + +```bash +# Create new script +cat > hack/scripts/my_script.sh << 'EOF' +#!/bin/bash +# Your script here +EOF + +# Make executable +chmod +x hack/scripts/my_script.sh +``` + +--- + +## Why "hack"? + +The `hack/` directory is a convention from Kubernetes and other projects for: +- Development utilities +- Test scripts +- Build helpers +- CI/CD scripts +- One-off tools + +It keeps the root directory clean while providing a place for development tools. diff --git a/hack/scripts/test_api.sh b/hack/scripts/test_api.sh new file mode 100755 index 0000000..8caf5d4 --- /dev/null +++ b/hack/scripts/test_api.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +echo "Testing PUMA OpenAI-Compatible API" +echo "====================================" +echo + +# Base URL +BASE_URL="http://localhost:8000" + +echo "1. Health Check" +curl -s "$BASE_URL/health" +echo -e "\n" + +echo "2. List Models" +curl -s "$BASE_URL/v1/models" | jq '.' +echo + +echo "3. Chat Completion (Non-streaming)" +curl -s "$BASE_URL/v1/chat/completions" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "test-model", + "messages": [ + {"role": "user", "content": "Hello!"} + ], + "max_tokens": 50 + }' | jq '.' +echo + +echo "4. Chat Completion (Streaming)" +curl -s -N "$BASE_URL/v1/chat/completions" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "test-model", + "messages": [ + {"role": "user", "content": "Tell me a story"} + ], + "stream": true, + "max_tokens": 50 + }' +echo -e "\n" + +echo "5. Legacy Text Completion" +curl -s "$BASE_URL/v1/completions" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "test-model", + "prompt": "Once upon a time", + "max_tokens": 50 + }' | jq '.' +echo + +echo "Done!" diff --git a/src/api/chat.rs b/src/api/chat.rs new file mode 100644 index 0000000..0dfcf4f --- /dev/null +++ b/src/api/chat.rs @@ -0,0 +1,254 @@ +use axum::{ + extract::State, + response::{ + sse::{Event, KeepAlive, Sse}, + IntoResponse, Response, + }, + Json, +}; +use futures::stream::StreamExt; +use std::sync::Arc; +use tokio_stream::wrappers::ReceiverStream; +use uuid::Uuid; + +use crate::api::routes::AppState; +use crate::api::types::{ + ChatChoice, ChatChoiceDelta, ChatCompletionChunk, ChatCompletionRequest, + ChatCompletionResponse, ChatMessage, ChatMessageDelta, ErrorResponse, Usage, +}; +use crate::backend::InferenceEngine; + +/// Main handler for chat completions +pub async fn chat_completions( + State(state): State>, + Json(req): Json, +) -> Response { + let engine = state.engine; + let registry = state.registry; + + // Validate request + if req.messages.is_empty() { + return ( + axum::http::StatusCode::BAD_REQUEST, + Json(ErrorResponse::new( + "messages cannot be empty".to_string(), + "invalid_request_error".to_string(), + )), + ) + .into_response(); + } + + // Validate model exists + match registry.get_model(&req.model) { + Ok(None) => { + return ( + axum::http::StatusCode::NOT_FOUND, + Json(ErrorResponse::new( + format!("Model '{}' not found", req.model), + "model_not_found".to_string(), + )), + ) + .into_response(); + } + Err(e) => { + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse::new( + format!("Failed to check model: {}", e), + "internal_error".to_string(), + )), + ) + .into_response(); + } + Ok(Some(_)) => { + // Model exists, continue + } + } + + if req.stream { + chat_completions_stream(engine, req).await.into_response() + } else { + match chat_completions_non_stream(engine, req).await { + Ok(response) => Json(response).into_response(), + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse::new( + err.to_string(), + "internal_error".to_string(), + )), + ) + .into_response(), + } + } +} + +/// Non-streaming chat completion +async fn chat_completions_non_stream( + engine: Arc, + req: ChatCompletionRequest, +) -> Result> { + let id = format!("chatcmpl-{}", Uuid::new_v4()); + let created = chrono::Utc::now().timestamp(); + + // Convert messages to prompt + let prompt = format_chat_messages(&req.messages); + + // Generate + let response = engine + .generate( + &req.model, + &prompt, + req.max_tokens.unwrap_or(100), + req.temperature.unwrap_or(0.7), + ) + .await?; + + Ok(ChatCompletionResponse { + id, + object: "chat.completion".to_string(), + created, + model: req.model, + choices: vec![ChatChoice { + index: 0, + message: ChatMessage { + role: "assistant".to_string(), + content: response.text, + }, + finish_reason: "stop".to_string(), + }], + usage: Usage { + prompt_tokens: response.prompt_tokens, + completion_tokens: response.completion_tokens, + total_tokens: response.prompt_tokens + response.completion_tokens, + }, + }) +} + +/// Streaming chat completion +async fn chat_completions_stream( + engine: Arc, + req: ChatCompletionRequest, +) -> Sse>> { + let id = format!("chatcmpl-{}", Uuid::new_v4()); + let created = chrono::Utc::now().timestamp(); + let model = req.model.clone(); + + let (tx, rx) = tokio::sync::mpsc::channel(100); + + // Spawn task to generate tokens + tokio::spawn(async move { + let prompt = format_chat_messages(&req.messages); + + // Send initial chunk with role + let initial_chunk = ChatCompletionChunk { + id: id.clone(), + object: "chat.completion.chunk".to_string(), + created, + model: model.clone(), + choices: vec![ChatChoiceDelta { + index: 0, + delta: ChatMessageDelta { + role: Some("assistant".to_string()), + content: None, + }, + finish_reason: None, + }], + }; + + if tx + .send(Ok( + Event::default().data(serde_json::to_string(&initial_chunk).unwrap()) + )) + .await + .is_err() + { + return; + } + + // Stream tokens + match engine + .generate_stream( + &model, + &prompt, + req.max_tokens.unwrap_or(100), + req.temperature.unwrap_or(0.7), + ) + .await + { + Ok(mut stream) => { + while let Some(token) = stream.next().await { + let chunk = ChatCompletionChunk { + id: id.clone(), + object: "chat.completion.chunk".to_string(), + created, + model: model.clone(), + choices: vec![ChatChoiceDelta { + index: 0, + delta: ChatMessageDelta { + role: None, + content: Some(token), + }, + finish_reason: None, + }], + }; + + if tx + .send(Ok( + Event::default().data(serde_json::to_string(&chunk).unwrap()) + )) + .await + .is_err() + { + break; + } + } + } + Err(e) => { + tracing::error!("Error generating stream: {}", e); + return; + } + } + + // Send final chunk + let final_chunk = ChatCompletionChunk { + id: id.clone(), + object: "chat.completion.chunk".to_string(), + created, + model: model.clone(), + choices: vec![ChatChoiceDelta { + index: 0, + delta: ChatMessageDelta { + role: None, + content: None, + }, + finish_reason: Some("stop".to_string()), + }], + }; + + let _ = tx + .send(Ok( + Event::default().data(serde_json::to_string(&final_chunk).unwrap()) + )) + .await; + let _ = tx.send(Ok(Event::default().data("[DONE]"))).await; + }); + + Sse::new(ReceiverStream::new(rx)).keep_alive(KeepAlive::default()) +} + +/// Format chat messages into a prompt +fn format_chat_messages(messages: &[ChatMessage]) -> String { + messages + .iter() + .map(|m| { + if m.role == "system" { + format!("System: {}", m.content) + } else if m.role == "user" { + format!("User: {}", m.content) + } else { + format!("Assistant: {}", m.content) + } + }) + .collect::>() + .join("\n") +} diff --git a/src/api/completions.rs b/src/api/completions.rs new file mode 100644 index 0000000..497736b --- /dev/null +++ b/src/api/completions.rs @@ -0,0 +1,113 @@ +use axum::{extract::State, response::IntoResponse, Json}; +use uuid::Uuid; + +use crate::api::routes::AppState; +use crate::api::types::{ + CompletionChoice, CompletionRequest, CompletionResponse, ErrorResponse, Usage, +}; +use crate::backend::InferenceEngine; + +/// Handler for legacy text completions +pub async fn completions( + State(state): State>, + Json(req): Json, +) -> impl IntoResponse { + let engine = state.engine; + let registry = state.registry; + + // Validate request + let prompt = req.prompt.to_string(); + if prompt.is_empty() { + return ( + axum::http::StatusCode::BAD_REQUEST, + Json(ErrorResponse::new( + "prompt cannot be empty".to_string(), + "invalid_request_error".to_string(), + )), + ) + .into_response(); + } + + // TODO: Implement streaming support for /v1/completions + if req.stream { + return ( + axum::http::StatusCode::BAD_REQUEST, + Json(ErrorResponse::new( + "Streaming not supported for /v1/completions endpoint".to_string(), + "invalid_request_error".to_string(), + )), + ) + .into_response(); + } + + // Validate model exists + match registry.get_model(&req.model) { + Ok(None) => { + return ( + axum::http::StatusCode::NOT_FOUND, + Json(ErrorResponse::new( + format!("Model '{}' not found", req.model), + "model_not_found".to_string(), + )), + ) + .into_response(); + } + Err(e) => { + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse::new( + format!("Failed to check model: {}", e), + "internal_error".to_string(), + )), + ) + .into_response(); + } + Ok(Some(_)) => { + // Model exists, continue + } + } + + let id = format!("cmpl-{}", Uuid::new_v4()); + let created = chrono::Utc::now().timestamp(); + + // Generate + match engine + .generate( + &req.model, + &prompt, + req.max_tokens.unwrap_or(100), + req.temperature.unwrap_or(0.7), + ) + .await + { + Ok(response) => { + let completion = CompletionResponse { + id, + object: "text_completion".to_string(), + created, + model: req.model, + choices: vec![CompletionChoice { + text: response.text, + index: 0, + logprobs: None, + finish_reason: "stop".to_string(), + }], + usage: Usage { + prompt_tokens: response.prompt_tokens, + completion_tokens: response.completion_tokens, + total_tokens: response.prompt_tokens + response.completion_tokens, + }, + }; + + Json(completion).into_response() + } + Err(e) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse::new( + e.to_string(), + "internal_error".to_string(), + )), + ) + .into_response(), + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..5de16ff --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,8 @@ +pub mod chat; +pub mod completions; +pub mod models; +pub mod routes; +pub mod types; + +#[cfg(test)] +mod tests; diff --git a/src/api/models.rs b/src/api/models.rs new file mode 100644 index 0000000..c8304e1 --- /dev/null +++ b/src/api/models.rs @@ -0,0 +1,80 @@ +use axum::{ + extract::{Path, State}, + response::IntoResponse, + Json, +}; + +use crate::api::routes::AppState; +use crate::api::types::{ErrorResponse, Model, ModelList}; +use crate::backend::InferenceEngine; + +/// List all available models +pub async fn list_models( + State(state): State>, +) -> impl IntoResponse { + let registry = state.registry; + match registry.load_models(None) { + Ok(models) => { + let model_list = ModelList { + object: "list".to_string(), + data: models + .into_iter() + .map(|m| Model { + id: m.name.clone(), + object: "model".to_string(), + created: chrono::DateTime::parse_from_rfc3339(&m.created_at) + .map(|dt| dt.timestamp()) + .unwrap_or(0), + owned_by: m.author.unwrap_or_else(|| "puma".to_string()), + }) + .collect(), + }; + Json(model_list).into_response() + } + Err(e) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse::new( + format!("Failed to load models: {}", e), + "internal_error".to_string(), + )), + ) + .into_response(), + } +} + +/// Get a specific model by ID +pub async fn get_model( + State(state): State>, + Path(model_id): Path, +) -> impl IntoResponse { + let registry = state.registry; + match registry.get_model(&model_id) { + Ok(Some(model)) => { + let model_info = Model { + id: model.name.clone(), + object: "model".to_string(), + created: chrono::DateTime::parse_from_rfc3339(&model.created_at) + .map(|dt| dt.timestamp()) + .unwrap_or(0), + owned_by: model.author.unwrap_or_else(|| "puma".to_string()), + }; + Json(model_info).into_response() + } + Ok(None) => ( + axum::http::StatusCode::NOT_FOUND, + Json(ErrorResponse::new( + format!("Model '{}' not found", model_id), + "model_not_found".to_string(), + )), + ) + .into_response(), + Err(e) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse::new( + format!("Failed to get model: {}", e), + "internal_error".to_string(), + )), + ) + .into_response(), + } +} diff --git a/src/api/routes.rs b/src/api/routes.rs new file mode 100644 index 0000000..403dd5b --- /dev/null +++ b/src/api/routes.rs @@ -0,0 +1,72 @@ +use axum::{ + routing::{get, post}, + Json, Router, +}; +use serde::Serialize; +use std::sync::Arc; +use tower_http::{ + cors::CorsLayer, + trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}, + LatencyUnit, +}; + +use crate::backend::InferenceEngine; +use crate::registry::model_registry::ModelRegistry; + +use super::{chat, completions, models}; + +/// Shared application state +#[derive(Clone)] +pub struct AppState { + pub engine: Arc, + pub registry: Arc, +} + +/// Create the API router with all endpoints +pub fn create_router( + engine: Arc, + registry: Arc, +) -> Router { + let state = AppState { engine, registry }; + + Router::new() + // Chat completions (most important) + .route("/v1/chat/completions", post(chat::chat_completions::)) + // Legacy completions + .route("/v1/completions", post(completions::completions::)) + // Models + .route("/v1/models", get(models::list_models::)) + .route("/v1/models/:model", get(models::get_model::)) + // Health check + .route("/health", get(health_check)) + // Pass state + .with_state(state) + // Enable request/response logging at INFO level + .layer( + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::new().level(tracing::Level::INFO)) + .on_request(DefaultOnRequest::new().level(tracing::Level::INFO)) + .on_response( + DefaultOnResponse::new() + .level(tracing::Level::INFO) + .latency_unit(LatencyUnit::Millis), + ), + ) + // Enable CORS for browser clients + .layer(CorsLayer::permissive()) +} + +/// Health check response +#[derive(Serialize)] +struct HealthResponse { + status: String, + version: String, +} + +/// Health check endpoint +async fn health_check() -> Json { + Json(HealthResponse { + status: "ok".to_string(), + version: env!("CARGO_PKG_VERSION").to_string(), + }) +} diff --git a/src/api/tests.rs b/src/api/tests.rs new file mode 100644 index 0000000..f5a44f2 --- /dev/null +++ b/src/api/tests.rs @@ -0,0 +1,438 @@ +//! API Integration Tests +//! +//! Tests the PUMA API endpoints using the Axum test utilities. +//! These tests verify the entire request/response cycle through the router. + +use axum::{ + body::Body, + http::{Request, StatusCode}, +}; +use serde_json::{json, Value}; +use std::sync::Arc; +use tempfile::TempDir; +use tower::util::ServiceExt; // for `oneshot` and `ready` + +use super::routes::create_router; +use crate::backend::mock::MockEngine; +use crate::registry::model_registry::{ArtifactInfo, ModelInfo, ModelMetadata, ModelRegistry}; + +/// Helper to create test app with a pre-registered test model +/// Returns the router and the temp directory (which must be kept alive) +fn create_test_app() -> (axum::Router, TempDir) { + let engine = Arc::new(MockEngine::new()); + let temp_dir = TempDir::new().unwrap(); + let registry = Arc::new(ModelRegistry::new(Some(temp_dir.path().to_path_buf()))); + + // Register a test model + let test_model = ModelInfo { + uuid: "test-uuid".to_string(), + name: "test-model".to_string(), + author: Some("test-author".to_string()), + task: Some("text-generation".to_string()), + model_series: Some("test-series".to_string()), + provider: "test".to_string(), + license: Some("MIT".to_string()), + created_at: chrono::Utc::now().to_rfc3339(), + updated_at: chrono::Utc::now().to_rfc3339(), + metadata: ModelMetadata { + artifact: ArtifactInfo { + revision: "test-rev".to_string(), + size: 1000, + path: "/tmp/test-model".to_string(), + }, + context_window: Some(2048), + safetensors: None, + }, + }; + + registry + .register_model(test_model) + .expect("failed to register test model"); + + (create_router(engine, registry), temp_dir) +} + +/// Helper to make a JSON request +async fn make_json_request( + app: axum::Router, + method: &str, + uri: &str, + body: Option, +) -> (StatusCode, Value) { + let mut request = Request::builder().uri(uri).method(method); + + if body.is_some() { + request = request.header("content-type", "application/json"); + } + + let request = if let Some(body) = body { + request.body(Body::from(serde_json::to_vec(&body).unwrap())) + } else { + request.body(Body::empty()) + } + .unwrap(); + + let response = app.oneshot(request).await.unwrap(); + + let status = response.status(); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + let json: Value = serde_json::from_slice(&body).unwrap_or(json!({})); + + // Debug output for failed requests + if !status.is_success() { + eprintln!("Request failed: {} {}", method, uri); + eprintln!("Status: {}", status); + eprintln!( + "Response: {}", + serde_json::to_string_pretty(&json).unwrap_or_default() + ); + } + + (status, json) +} + +#[tokio::test] +async fn test_health_check() { + let (app, _temp_dir) = create_test_app(); + let (status, json) = make_json_request(app, "GET", "/health", None).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(json["status"], "ok"); + assert!(json["version"].is_string()); +} + +#[tokio::test] +async fn test_list_models() { + let (app, _temp_dir) = create_test_app(); + let (status, json) = make_json_request(app, "GET", "/v1/models", None).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(json["object"], "list"); + assert!(json["data"].is_array()); +} + +#[tokio::test] +async fn test_chat_completion_non_streaming() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "messages": [ + {"role": "user", "content": "Hello"} + ], + "max_tokens": 50, + "stream": false + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/chat/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(json["object"], "chat.completion"); + assert!(json["id"].is_string()); + assert_eq!(json["model"], "test-model"); + assert!(json["choices"].is_array()); + assert_eq!(json["choices"][0]["index"], 0); + assert_eq!(json["choices"][0]["message"]["role"], "assistant"); + assert!(json["choices"][0]["message"]["content"].is_string()); + assert_eq!(json["choices"][0]["finish_reason"], "stop"); + assert!(json["usage"]["prompt_tokens"].is_number()); + assert!(json["usage"]["completion_tokens"].is_number()); + assert!(json["usage"]["total_tokens"].is_number()); +} + +#[tokio::test] +async fn test_chat_completion_empty_messages() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "messages": [], + "stream": false + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/chat/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(json["error"]["type"], "invalid_request_error"); + assert!(json["error"]["message"] + .as_str() + .unwrap() + .contains("messages cannot be empty")); +} + +#[tokio::test] +async fn test_text_completion() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "prompt": "Once upon a time", + "max_tokens": 50 + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(json["object"], "text_completion"); + assert!(json["id"].is_string()); + assert_eq!(json["model"], "test-model"); + assert!(json["choices"].is_array()); + assert_eq!(json["choices"][0]["index"], 0); + assert!(json["choices"][0]["text"].is_string()); + assert_eq!(json["choices"][0]["finish_reason"], "stop"); + assert!(json["usage"]["prompt_tokens"].is_number()); +} + +#[tokio::test] +async fn test_text_completion_empty_prompt() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "prompt": "" + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(json["error"]["type"], "invalid_request_error"); + assert!(json["error"]["message"] + .as_str() + .unwrap() + .contains("prompt cannot be empty")); +} + +#[tokio::test] +async fn test_text_completion_streaming_not_supported() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "prompt": "Hello world", + "stream": true + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(json["error"]["type"], "invalid_request_error"); + assert!(json["error"]["message"] + .as_str() + .unwrap() + .contains("Streaming not supported")); +} + +#[tokio::test] +async fn test_chat_completion_with_system_message() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello"} + ], + "max_tokens": 50 + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/chat/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(json["object"], "chat.completion"); + assert!(json["choices"][0]["message"]["content"].is_string()); +} + +#[tokio::test] +async fn test_chat_completion_with_temperature() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "messages": [ + {"role": "user", "content": "Hello"} + ], + "temperature": 0.5, + "max_tokens": 50 + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/chat/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(json["object"], "chat.completion"); +} + +#[tokio::test] +async fn test_chat_completion_default_values() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "messages": [ + {"role": "user", "content": "Hello"} + ] + // No max_tokens, temperature, etc. - should use defaults + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/chat/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(json["object"], "chat.completion"); +} + +#[tokio::test] +async fn test_cors_headers() { + let (app, _temp_dir) = create_test_app(); + let request = Request::builder() + .uri("/health") + .method("GET") + .header("Origin", "https://example.com") + .body(Body::empty()) + .unwrap(); + + let response = app.oneshot(request).await.unwrap(); + + // CORS should be permissive + assert!(response + .headers() + .contains_key("access-control-allow-origin")); +} + +#[tokio::test] +async fn test_invalid_route() { + let (app, _temp_dir) = create_test_app(); + let request = Request::builder() + .uri("/invalid/route") + .method("GET") + .body(Body::empty()) + .unwrap(); + + let response = app.oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn test_method_not_allowed() { + let (app, _temp_dir) = create_test_app(); + // Try POST on GET-only endpoint + let request = Request::builder() + .uri("/health") + .method("POST") + .body(Body::empty()) + .unwrap(); + + let response = app.oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); +} + +#[tokio::test] +async fn test_chat_completion_nonexistent_model() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "nonexistent-model", + "messages": [ + {"role": "user", "content": "Hello"} + ], + "max_tokens": 50 + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/chat/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::NOT_FOUND); + assert_eq!(json["error"]["type"], "model_not_found"); + assert!(json["error"]["message"] + .as_str() + .unwrap() + .contains("nonexistent-model")); +} + +#[tokio::test] +async fn test_text_completion_nonexistent_model() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "nonexistent-model", + "prompt": "Hello world" + }); + + let (status, json) = + make_json_request(app, "POST", "/v1/completions", Some(request_body)).await; + + assert_eq!(status, StatusCode::NOT_FOUND); + assert_eq!(json["error"]["type"], "model_not_found"); + assert!(json["error"]["message"] + .as_str() + .unwrap() + .contains("nonexistent-model")); +} + +#[tokio::test] +async fn test_chat_completion_streaming() { + let (app, _temp_dir) = create_test_app(); + let request_body = json!({ + "model": "test-model", + "messages": [ + {"role": "user", "content": "Hello"} + ], + "max_tokens": 50, + "stream": true + }); + + let request = Request::builder() + .uri("/v1/chat/completions") + .method("POST") + .header("content-type", "application/json") + .body(Body::from(serde_json::to_vec(&request_body).unwrap())) + .unwrap(); + + let response = app.oneshot(request).await.unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response.headers().get("content-type").unwrap(), + "text/event-stream" + ); + + // Read the streaming body + let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + let body_text = String::from_utf8_lossy(&body_bytes); + + // Parse SSE events (format: "data: {...}\n\n") + let events: Vec<&str> = body_text + .split("\n\n") + .filter(|line| line.starts_with("data: ")) + .map(|line| line.strip_prefix("data: ").unwrap()) + .collect(); + + assert!(!events.is_empty(), "Should have at least one event"); + + // Separate JSON events from [DONE] + let json_events: Vec<&str> = events.iter().filter(|&&e| e != "[DONE]").copied().collect(); + + assert!( + !json_events.is_empty(), + "Should have at least one JSON event" + ); + + // Check first event has role + let first_event: Value = serde_json::from_str(json_events[0]).unwrap(); + assert_eq!(first_event["object"], "chat.completion.chunk"); + assert_eq!(first_event["model"], "test-model"); + assert_eq!(first_event["choices"][0]["delta"]["role"], "assistant"); + + // Check middle events have content + if json_events.len() > 2 { + let middle_event: Value = serde_json::from_str(json_events[1]).unwrap(); + assert!(middle_event["choices"][0]["delta"]["content"].is_string()); + } + + // Check last JSON event has finish_reason + let last_json_event: Value = serde_json::from_str(json_events[json_events.len() - 1]).unwrap(); + assert_eq!(last_json_event["choices"][0]["finish_reason"], "stop"); + + // Check stream ends with [DONE] + assert!(events.contains(&"[DONE]"), "Stream should end with [DONE]"); +} diff --git a/src/api/types/mod.rs b/src/api/types/mod.rs new file mode 100644 index 0000000..6415885 --- /dev/null +++ b/src/api/types/mod.rs @@ -0,0 +1,5 @@ +pub mod request; +pub mod response; + +pub use request::*; +pub use response::*; diff --git a/src/api/types/request.rs b/src/api/types/request.rs new file mode 100644 index 0000000..17e2556 --- /dev/null +++ b/src/api/types/request.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Serialize}; + +/// Chat completion request (OpenAI compatible) +#[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] +pub struct ChatCompletionRequest { + pub model: String, + pub messages: Vec, + #[serde(default = "default_max_tokens")] + pub max_tokens: Option, + #[serde(default = "default_temperature")] + pub temperature: Option, + #[serde(default)] + pub top_p: Option, + #[serde(default)] + pub n: Option, + #[serde(default)] + pub stream: bool, + pub stop: Option, + #[serde(default)] + pub presence_penalty: Option, + #[serde(default)] + pub frequency_penalty: Option, +} + +/// Chat message +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatMessage { + pub role: String, // "system", "user", "assistant" + pub content: String, +} + +/// Legacy text completion request +#[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] +pub struct CompletionRequest { + pub model: String, + #[serde(alias = "prompt")] + pub prompt: StringOrArray, + #[serde(default = "default_max_tokens")] + pub max_tokens: Option, + #[serde(default = "default_temperature")] + pub temperature: Option, + #[serde(default)] + pub top_p: Option, + #[serde(default)] + pub n: Option, + #[serde(default)] + pub stream: bool, + pub stop: Option, + #[serde(default)] + pub presence_penalty: Option, + #[serde(default)] + pub frequency_penalty: Option, +} + +/// Prompt can be string or array of strings +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum StringOrArray { + String(String), + Array(Vec), +} + +impl std::fmt::Display for StringOrArray { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StringOrArray::String(s) => write!(f, "{}", s), + StringOrArray::Array(arr) => write!(f, "{}", arr.join("\n")), + } + } +} + +// Default values +fn default_max_tokens() -> Option { + Some(100) +} + +fn default_temperature() -> Option { + Some(0.7) +} diff --git a/src/api/types/response.rs b/src/api/types/response.rs new file mode 100644 index 0000000..462ca03 --- /dev/null +++ b/src/api/types/response.rs @@ -0,0 +1,119 @@ +use serde::{Deserialize, Serialize}; + +use super::request::ChatMessage; + +/// Chat completion response +#[derive(Debug, Serialize)] +pub struct ChatCompletionResponse { + pub id: String, + pub object: String, // "chat.completion" + pub created: i64, + pub model: String, + pub choices: Vec, + pub usage: Usage, +} + +/// Choice in chat completion +#[derive(Debug, Serialize)] +pub struct ChatChoice { + pub index: usize, + pub message: ChatMessage, + pub finish_reason: String, // "stop", "length", "content_filter" +} + +/// Streaming chat completion chunk +#[derive(Debug, Serialize)] +pub struct ChatCompletionChunk { + pub id: String, + pub object: String, // "chat.completion.chunk" + pub created: i64, + pub model: String, + pub choices: Vec, +} + +/// Delta choice for streaming +#[derive(Debug, Serialize)] +pub struct ChatChoiceDelta { + pub index: usize, + pub delta: ChatMessageDelta, + #[serde(skip_serializing_if = "Option::is_none")] + pub finish_reason: Option, +} + +/// Delta message for streaming +#[derive(Debug, Serialize)] +pub struct ChatMessageDelta { + #[serde(skip_serializing_if = "Option::is_none")] + pub role: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, +} + +/// Legacy completion response +#[derive(Debug, Serialize)] +pub struct CompletionResponse { + pub id: String, + pub object: String, // "text_completion" + pub created: i64, + pub model: String, + pub choices: Vec, + pub usage: Usage, +} + +/// Choice in completion +#[derive(Debug, Serialize)] +pub struct CompletionChoice { + pub text: String, + pub index: usize, + pub logprobs: Option, + pub finish_reason: String, +} + +/// Token usage statistics +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Usage { + pub prompt_tokens: usize, + pub completion_tokens: usize, + pub total_tokens: usize, +} + +/// Model list response +#[derive(Debug, Serialize)] +pub struct ModelList { + pub object: String, // "list" + pub data: Vec, +} + +/// Model information +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Model { + pub id: String, + pub object: String, // "model" + pub created: i64, + pub owned_by: String, +} + +/// Error response +#[derive(Debug, Serialize)] +pub struct ErrorResponse { + pub error: ErrorDetail, +} + +#[derive(Debug, Serialize)] +pub struct ErrorDetail { + pub message: String, + pub r#type: String, + pub code: Option, +} + +impl ErrorResponse { + pub fn new(message: String, error_type: String) -> Self { + Self { + error: ErrorDetail { + message, + r#type: error_type, + code: None, + }, + } + } +} diff --git a/src/backend/engine.rs b/src/backend/engine.rs new file mode 100644 index 0000000..2d464a5 --- /dev/null +++ b/src/backend/engine.rs @@ -0,0 +1,34 @@ +use std::io; +use std::pin::Pin; +use tokio_stream::Stream; + +/// Inference engine trait +pub trait InferenceEngine: Send + Sync { + /// Generate text completion + fn generate( + &self, + model: &str, + prompt: &str, + max_tokens: usize, + temperature: f32, + ) -> impl std::future::Future> + Send; + + /// Generate text with streaming + fn generate_stream( + &self, + model: &str, + prompt: &str, + max_tokens: usize, + temperature: f32, + ) -> impl std::future::Future< + Output = Result + Send>>, io::Error>, + > + Send; +} + +/// Generation response +#[derive(Debug, Clone)] +pub struct GenerateResponse { + pub text: String, + pub prompt_tokens: usize, + pub completion_tokens: usize, +} diff --git a/src/backend/mock.rs b/src/backend/mock.rs new file mode 100644 index 0000000..92f056e --- /dev/null +++ b/src/backend/mock.rs @@ -0,0 +1,74 @@ +use futures::stream::{self, StreamExt}; +use std::io; +use std::pin::Pin; +use tokio_stream::Stream; + +use super::engine::{GenerateResponse, InferenceEngine}; + +/// Mock engine for testing (replace with MLX later) +#[derive(Clone)] +pub struct MockEngine; + +impl MockEngine { + pub fn new() -> Self { + Self + } +} + +impl InferenceEngine for MockEngine { + async fn generate( + &self, + model: &str, + prompt: &str, + max_tokens: usize, + _temperature: f32, + ) -> Result { + // Mock response for testing + let response_text = format!( + "This is a mock response from model '{}' for prompt: '{}' (max_tokens: {})", + model, + prompt.chars().take(50).collect::(), + max_tokens + ); + + Ok(GenerateResponse { + text: response_text, + prompt_tokens: prompt.split_whitespace().count(), + completion_tokens: 20, + }) + } + + async fn generate_stream( + &self, + model: &str, + _prompt: &str, + max_tokens: usize, + _temperature: f32, + ) -> Result + Send>>, io::Error> { + // Mock streaming response + let tokens = vec![ + "This ".to_string(), + "is ".to_string(), + "a ".to_string(), + "mock ".to_string(), + "streaming ".to_string(), + "response ".to_string(), + format!("from model '{}' ", model), + format!("(max_tokens: {}).", max_tokens), + ]; + + // Simulate delay between tokens + let stream = stream::iter(tokens).then(|token| async move { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + token + }); + + Ok(Box::pin(stream)) + } +} + +impl Default for MockEngine { + fn default() -> Self { + Self::new() + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..1631447 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,4 @@ +pub mod engine; +pub mod mock; + +pub use engine::*; diff --git a/src/cli/commands.rs b/src/cli/commands.rs index f07be0a..ae37255 100644 --- a/src/cli/commands.rs +++ b/src/cli/commands.rs @@ -37,6 +37,19 @@ enum Commands { INSPECT(InspectArgs), /// Returns the version of PUMA. VERSION, + /// Start the inference server + SERVE(ServeArgs), +} + +#[derive(Parser)] +struct ServeArgs { + /// Host address to bind to + #[arg(long, default_value = "0.0.0.0")] + host: String, + + /// Port to listen on + #[arg(short, long, default_value = "8000")] + port: u16, } #[derive(Parser)] @@ -204,6 +217,13 @@ pub async fn run(cli: Cli) { Commands::VERSION => { println!("PUMA {}", env!("CARGO_PKG_VERSION")); } + + Commands::SERVE(args) => { + if let Err(e) = crate::cli::serve::execute(&args.host, args.port).await { + eprintln!("Error starting server: {}", e); + std::process::exit(1); + } + } } } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 65f2452..fbfc92f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -2,3 +2,4 @@ pub mod commands; pub mod inspect; pub mod ls; pub mod rm; +pub mod serve; diff --git a/src/cli/serve.rs b/src/cli/serve.rs new file mode 100644 index 0000000..d0bb814 --- /dev/null +++ b/src/cli/serve.rs @@ -0,0 +1,58 @@ +use colored::Colorize; +use std::sync::Arc; +use tracing::{debug, info}; + +use crate::api::routes::create_router; +use crate::backend::mock::MockEngine; +use crate::registry::model_registry::ModelRegistry; + +/// Execute the serve command +pub async fn execute(host: &str, port: u16) -> Result<(), Box> { + println!( + "{}", + " + ███████████ █████ █████ ██████ ██████ █████████ +░░███░░░░░███░░███ ░░███ ░░██████ ██████ ███░░░░░███ + ░███ ░███ ░███ ░███ ░███░█████░███ ░███ ░███ + ░██████████ ░███ ░███ ░███░░███ ░███ ░███████████ + ░███░░░░░░ ░███ ░███ ░███ ░░░ ░███ ░███░░░░░███ + ░███ ░███ ░███ ░███ ░███ ░███ ░███ + █████ ░░████████ █████ █████ █████ █████ +░░░░░ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ + " + .bright_blue() + .bold() + ); + info!("Starting PUMA inference server"); + + // Initialize backend (MockEngine for now, replace with MLX later) + let engine = Arc::new(MockEngine::new()); + info!("Inference engine initialized"); + debug!("Using MockEngine backend"); + + // Initialize model registry + let registry = Arc::new(ModelRegistry::new(None)); + info!("Model registry loaded"); + + // Create router + let app = create_router(engine, registry); + + // Bind address + let addr = format!("{}:{}", host, port); + let listener = tokio::net::TcpListener::bind(&addr).await?; + + info!("Server listening on http://{}", addr); + info!("Available endpoints:"); + info!(" POST /v1/chat/completions"); + info!(" POST /v1/completions"); + info!(" GET /v1/models"); + info!(" GET /v1/models/:model"); + info!(" GET /health"); + + // Start server + debug!("Starting axum server"); + axum::serve(listener, app).await?; + + info!("Server shutdown"); + Ok(()) +} diff --git a/src/downloader/huggingface.rs b/src/downloader/huggingface.rs index 72ffae1..8d7ec73 100644 --- a/src/downloader/huggingface.rs +++ b/src/downloader/huggingface.rs @@ -1,5 +1,5 @@ use colored::Colorize; -use log::debug; +use tracing::debug; use hf_hub::api::tokio::{ApiBuilder, Progress}; use indicatif::{ProgressBar, ProgressStyle}; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 4e70de3..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -// lib.rs is intentionally minimal - puma is a binary-first application -// Internal modules are not exposed as public API diff --git a/src/main.rs b/src/main.rs index f57d893..614a698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +mod api; +mod backend; mod cli; mod downloader; mod registry; @@ -12,18 +14,24 @@ use crate::cli::commands::{run, Cli}; use crate::utils::file; fn main() { - // Initialize logger. - env_logger::Builder::from_env(env_logger::Env::default()).init(); + // Setup tracing subscriber for tower-http TraceLayer + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "info,hf_hub=warn,tower_http=info".into()), + ) + .init(); // Create the root folder if it doesn't exist. file::create_folder_if_not_exists(&file::root_home()).unwrap(); + let cli = Cli::parse(); + let runtime = Builder::new_multi_thread() .worker_threads(4) .enable_all() .build() .unwrap(); - let cli = Cli::parse(); runtime.block_on(run(cli)); } diff --git a/src/storage/storage_trait.rs b/src/storage/storage_trait.rs index 3968704..a39395b 100644 --- a/src/storage/storage_trait.rs +++ b/src/storage/storage_trait.rs @@ -4,7 +4,7 @@ use std::io; use std::collections::HashMap; /// Trait for model storage backends -pub trait ModelStorage { +pub trait ModelStorage: Send + Sync { /// Load models from storage with optional filtering by column values (e.g., author=InftyAI, license=mit) fn load_models( &self, diff --git a/tests/integration_test.rs b/tests/cli_test.rs similarity index 97% rename from tests/integration_test.rs rename to tests/cli_test.rs index 83307f9..a2e1593 100644 --- a/tests/integration_test.rs +++ b/tests/cli_test.rs @@ -1,3 +1,8 @@ +//! CLI Integration Tests +//! +//! Tests the PUMA command-line interface by executing the binary +//! and verifying output. These tests use real processes and temporary directories. + use std::process::Command; use tempfile::TempDir;