diff --git a/Cargo.lock b/Cargo.lock index 3a67aa1..ad24aa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,12 +330,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -416,16 +410,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "compact_str" version = "0.9.0" @@ -486,7 +470,6 @@ dependencies = [ "futures", "log", "poc-memory", - "reqwest", "serde", "serde_json", "tokio", @@ -736,17 +719,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "document-features" version = "0.2.12" @@ -1054,10 +1026,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1067,11 +1037,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] @@ -1193,43 +1161,18 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", "bytes", - "futures-channel", - "futures-util", "http", "http-body", "hyper", - "ipnet", - "libc", - "percent-encoding", "pin-project-lite", - "socket2", "tokio", - "tower-service", - "tracing", ] [[package]] @@ -1256,88 +1199,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - [[package]] name = "id-arena" version = "2.3.0" @@ -1350,27 +1211,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - [[package]] name = "indexmap" version = "2.13.1" @@ -1411,22 +1251,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -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_polyfill" version = "1.70.2" @@ -1472,50 +1296,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.117", -] - [[package]] name = "jobkit" version = "0.3.0" @@ -1625,12 +1405,6 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - [[package]] name = "litrs" version = "1.0.0" @@ -1661,12 +1435,6 @@ dependencies = [ "hashbrown 0.16.1", ] -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "mac_address" version = "1.1.8" @@ -2012,7 +1780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand", ] [[package]] @@ -2069,6 +1837,7 @@ dependencies = [ "anyhow", "base64", "bincode", + "bytes", "capnp", "capnp-rpc", "capnpc", @@ -2080,6 +1849,10 @@ dependencies = [ "figment", "futures", "glob", + "http", + "http-body-util", + "hyper", + "hyper-util", "jobkit", "json5", "libc", @@ -2092,13 +1865,16 @@ dependencies = [ "rayon", "redb", "regex", - "reqwest", "rkyv", + "rustls", + "rustls-native-certs", "serde", "serde_json", + "serde_urlencoded", "skillratings", "tiktoken-rs", "tokio", + "tokio-rustls", "tokio-scoped", "tokio-util", "tui-markdown", @@ -2121,30 +1897,12 @@ dependencies = [ "portable-atomic", ] -[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "pretty_assertions" version = "1.4.1" @@ -2263,62 +2021,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.1.2", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash 2.1.2", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - [[package]] name = "quote" version = "1.0.45" @@ -2352,27 +2054,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", + "rand_core", ] [[package]] @@ -2381,15 +2063,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "ratatui" version = "0.30.0" @@ -2568,44 +2241,6 @@ dependencies = [ "bytecheck", ] -[[package]] -name = "reqwest" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" -dependencies = [ - "base64", - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "ring" version = "0.17.14" @@ -2684,12 +2319,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - [[package]] name = "rustc_version" version = "0.4.1" @@ -2746,37 +2375,9 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "web-time", "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.103.10" @@ -3009,12 +2610,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "static_assertions" version = "1.1.0" @@ -3076,26 +2671,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "syntect" version = "5.3.0" @@ -3238,7 +2813,7 @@ dependencies = [ "fancy-regex 0.13.0", "lazy_static", "regex", - "rustc-hash 1.1.0", + "rustc-hash", ] [[package]] @@ -3274,16 +2849,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" version = "1.11.0" @@ -3402,51 +2967,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags 2.11.0", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" version = "0.1.44" @@ -3582,24 +3102,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -3689,20 +3191,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" -dependencies = [ - "cfg-if", - "futures-util", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.114" @@ -3769,35 +3257,6 @@ dependencies = [ "semver", ] -[[package]] -name = "web-sys" -version = "0.3.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "1.0.6" @@ -3969,31 +3428,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -4005,192 +3446,70 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "1.0.1" @@ -4288,12 +3607,6 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - [[package]] name = "wyz" version = "0.5.1" @@ -4318,109 +3631,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "zerofrom" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", -] - [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index c831e17..0953443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,16 +19,23 @@ version.workspace = true edition.workspace = true [dependencies] -capnp = "0.25" +figment = { version = "0.10", features = ["env"] } +env_logger = "0.11" uuid = { version = "1", features = ["v4"] } +clap = { version = "4", features = ["derive"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +crossterm = { version = "0.29", features = ["event-stream"] } +ratatui = { version = "0.30", features = ["unstable-rendered-line-info"] } +tui-markdown = "0.3" +tui-textarea = { version = "0.10.2", package = "tui-textarea-2" } json5 = "1.3" bincode = "1" regex = "1" +glob = "0.3" chrono = "0.4" -clap = { version = "4", features = ["derive"] } libc = "0.2" +redb = "2" rkyv = { version = "0.7", features = ["validation", "std"] } memchr = "2" memmap2 = "0.9" @@ -37,25 +44,26 @@ peg = "0.8" paste = "1" jobkit = { git = "https://evilpiepirate.org/git/jobkit.git", features = ["daemon"] } tokio = { version = "1", features = ["full"] } -reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } -glob = "0.3" +tokio-util = { version = "0.7", features = ["compat"] } +tokio-scoped = "0.2.0" +futures = "0.3" +capnp = "0.25" +capnp-rpc = "0.25" +http = "1" +hyper = { version = "1", features = ["client", "http1"] } +hyper-util = { version = "0.1", features = ["tokio"], default-features = false } +http-body-util = "0.1" +rustls = "0.23" +tokio-rustls = "0.26" +rustls-native-certs = "0.8" +serde_urlencoded = "0.7" +bytes = "1" anyhow = "1" base64 = "0.22" dirs = "6" -futures = "0.3" tiktoken-rs = "0.9.1" -figment = { version = "0.10", features = ["env"] } -tui-markdown = "0.3" -tui-textarea = { version = "0.10.2", package = "tui-textarea-2" } -redb = "2" log = "0.4" -ratatui = { version = "0.30", features = ["unstable-rendered-line-info"] } -crossterm = { version = "0.29", features = ["event-stream"] } skillratings = "0.28" -capnp-rpc = "0.25" -tokio-util = { version = "0.7", features = ["compat"] } -env_logger = "0.11" -tokio-scoped = "0.2.0" [build-dependencies] capnpc = "0.25" diff --git a/channels/telegram/Cargo.toml b/channels/telegram/Cargo.toml index afc4bb0..902453e 100644 --- a/channels/telegram/Cargo.toml +++ b/channels/telegram/Cargo.toml @@ -9,7 +9,6 @@ capnp-rpc = "0.25" dirs = "6" futures = "0.3" poc-memory = { path = "../.." } -reqwest = { version = "0.13", default-features = false, features = ["json", "form", "rustls"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["full"] } diff --git a/channels/telegram/src/main.rs b/channels/telegram/src/main.rs index abe5598..3236fa8 100644 --- a/channels/telegram/src/main.rs +++ b/channels/telegram/src/main.rs @@ -64,7 +64,7 @@ struct State { /// Telegram API offset last_offset: i64, connected: bool, - client: reqwest::Client, + client: poc_memory::agent::api::http::HttpClient, /// Registered notification callbacks subscribers: Vec, } @@ -79,7 +79,7 @@ impl State { channel_logs: std::collections::BTreeMap::new(), last_offset, connected: false, - client: reqwest::Client::new(), + client: poc_memory::agent::api::http::HttpClient::new(), subscribers: Vec::new(), } } @@ -172,9 +172,7 @@ async fn poll_once(state: &SharedState) -> Result<(), Box }; let client = state.borrow().client.clone(); - let resp: serde_json::Value = client.get(&url) - .timeout(std::time::Duration::from_secs(35)) - .send().await?.json().await?; + let resp: serde_json::Value = client.get(&url).await?.json().await?; if !state.borrow().connected { state.borrow_mut().connected = true; @@ -199,9 +197,10 @@ async fn poll_once(state: &SharedState) -> Result<(), Box let msg_chat_id = msg["chat"]["id"].as_i64().unwrap_or(0); if msg_chat_id != chat_id { let reject_url = format!("https://api.telegram.org/bot{token}/sendMessage"); - let _ = client.post(&reject_url) - .form(&[("chat_id", msg_chat_id.to_string()), ("text", "This is a private bot.".to_string())]) - .send().await; + let _ = client.post_form(&reject_url, &[ + ("chat_id", &msg_chat_id.to_string()), + ("text", "This is a private bot."), + ]).await; continue; } @@ -273,9 +272,10 @@ impl channel_server::Server for ChannelServerImpl { let s = state.borrow(); (s.api_url("sendMessage"), s.client.clone(), s.config.chat_id) }; - let _ = client.post(&url) - .form(&[("chat_id", &chat_id.to_string()), ("text", &message)]) - .send().await; + let _ = client.post_form(&url, &[ + ("chat_id", &chat_id.to_string()), + ("text", &message), + ]).await; let ts = now() as u64; append_history(&format!("{ts} [agent] {message}")); diff --git a/src/agent/api/http.rs b/src/agent/api/http.rs new file mode 100644 index 0000000..c11efdd --- /dev/null +++ b/src/agent/api/http.rs @@ -0,0 +1,239 @@ +// http.rs — Minimal async HTTP client +// +// Replaces reqwest with direct hyper + rustls. No tracing dependency. +// Supports: GET/POST, JSON/form bodies, streaming responses, TLS. + +use anyhow::{Context, Result}; +use bytes::Bytes; +use http_body_util::{BodyExt, Full, Empty}; +use hyper::body::Incoming; +use hyper::{Request, StatusCode}; +use hyper_util::rt::TokioIo; +use rustls::ClientConfig; +use std::sync::Arc; +use std::time::Duration; +use tokio::net::TcpStream; + +/// Lightweight async HTTP client with connection pooling via keep-alive. +#[derive(Clone)] +pub struct HttpClient { + tls: Arc, + connect_timeout: Duration, + request_timeout: Duration, +} + +/// An in-flight response — provides status, headers, and body access. +pub struct HttpResponse { + parts: http::response::Parts, + body: Incoming, +} + +impl HttpClient { + pub fn new() -> Self { + Self::builder().build() + } + + pub fn builder() -> HttpClientBuilder { + HttpClientBuilder { + connect_timeout: Duration::from_secs(30), + request_timeout: Duration::from_secs(600), + } + } + + /// Send a GET request. + pub async fn get(&self, url: &str) -> Result { + self.get_with_headers(url, &[]).await + } + + /// Send a GET request with custom headers. + pub async fn get_with_headers(&self, url: &str, headers: &[(&str, &str)]) -> Result { + let mut builder = Request::get(url); + for &(k, v) in headers { + builder = builder.header(k, v); + } + let req = builder.body(Empty::::new()) + .context("building GET request")?; + self.send_empty(req).await + } + + /// Send a POST request with a JSON body. + pub async fn post_json(&self, url: &str, body: &impl serde::Serialize) -> Result { + let json = serde_json::to_vec(body).context("serializing JSON body")?; + let req = Request::post(url) + .header("content-type", "application/json") + .body(Full::new(Bytes::from(json))) + .context("building POST request")?; + self.send_full(req).await + } + + /// Send a POST request with URL-encoded form data. + pub async fn post_form(&self, url: &str, params: &[(&str, &str)]) -> Result { + let body = serde_urlencoded::to_string(params).context("encoding form")?; + let req = Request::post(url) + .header("content-type", "application/x-www-form-urlencoded") + .body(Full::new(Bytes::from(body))) + .context("building form POST")?; + self.send_full(req).await + } + + /// Send a request with headers pre-set. JSON body. + pub async fn send_json( + &self, + method: &str, + url: &str, + headers: &[(&str, &str)], + body: &impl serde::Serialize, + ) -> Result { + let json = serde_json::to_vec(body).context("serializing JSON body")?; + let mut builder = Request::builder() + .method(method) + .uri(url) + .header("content-type", "application/json"); + for &(k, v) in headers { + builder = builder.header(k, v); + } + let req = builder.body(Full::new(Bytes::from(json))) + .context("building request")?; + self.send_full(req).await + } + + async fn connect(&self, url: &str) -> Result<(bool, TokioIo>)> { + let uri: http::Uri = url.parse().context("parsing URL")?; + let host = uri.host().context("URL has no host")?.to_string(); + let is_https = uri.scheme_str() == Some("https"); + let port = uri.port_u16().unwrap_or(if is_https { 443 } else { 80 }); + + let tcp = tokio::time::timeout( + self.connect_timeout, + TcpStream::connect(format!("{}:{}", host, port)), + ).await + .context("connect timeout")? + .context("TCP connect")?; + + if is_https { + let server_name = rustls::pki_types::ServerName::try_from(host.clone()) + .map_err(|e| anyhow::anyhow!("invalid server name: {}", e))?; + let connector = tokio_rustls::TlsConnector::from(self.tls.clone()); + let tls = connector.connect(server_name.to_owned(), tcp).await + .context("TLS handshake")?; + Ok((is_https, TokioIo::new(Box::new(tls) as Box))) + } else { + Ok((is_https, TokioIo::new(Box::new(tcp) as Box))) + } + } + + async fn send_full(&self, req: Request>) -> Result { + let url = req.uri().to_string(); + let (_is_https, io) = self.connect(&url).await?; + + let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await + .context("HTTP handshake")?; + tokio::spawn(conn); + + let resp = tokio::time::timeout( + self.request_timeout, + sender.send_request(req), + ).await + .context("request timeout")? + .context("sending request")?; + + let (parts, body) = resp.into_parts(); + Ok(HttpResponse { parts, body }) + } + + async fn send_empty(&self, req: Request>) -> Result { + let url = req.uri().to_string(); + let (_is_https, io) = self.connect(&url).await?; + + let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await + .context("HTTP handshake")?; + tokio::spawn(conn); + + let resp = tokio::time::timeout( + self.request_timeout, + sender.send_request(req), + ).await + .context("request timeout")? + .context("sending request")?; + + let (parts, body) = resp.into_parts(); + Ok(HttpResponse { parts, body }) + } +} + +impl HttpResponse { + pub fn status(&self) -> StatusCode { + self.parts.status + } + + pub fn header(&self, name: &str) -> Option<&str> { + self.parts.headers.get(name)?.to_str().ok() + } + + /// Read the entire body as text. + pub async fn text(self) -> Result { + let bytes = self.body.collect().await + .context("reading response body")? + .to_bytes(); + Ok(String::from_utf8_lossy(&bytes).into_owned()) + } + + /// Read the entire body and deserialize as JSON. + pub async fn json(self) -> Result { + let bytes = self.body.collect().await + .context("reading response body")? + .to_bytes(); + serde_json::from_slice(&bytes).context("deserializing JSON response") + } + + /// Read the next chunk from the response body (for SSE streaming). + /// Returns None when the body is complete. + pub async fn chunk(&mut self) -> Result> { + match self.body.frame().await { + Some(Ok(frame)) => Ok(frame.into_data().ok()), + Some(Err(e)) => Err(anyhow::anyhow!("body read error: {}", e)), + None => Ok(None), + } + } +} + +pub struct HttpClientBuilder { + connect_timeout: Duration, + request_timeout: Duration, +} + +impl HttpClientBuilder { + pub fn connect_timeout(mut self, d: Duration) -> Self { + self.connect_timeout = d; + self + } + + pub fn timeout(mut self, d: Duration) -> Self { + self.request_timeout = d; + self + } + + pub fn build(self) -> HttpClient { + let certs = rustls_native_certs::load_native_certs() + .certs.into_iter() + .collect::>(); + let mut root_store = rustls::RootCertStore::empty(); + for cert in certs { + root_store.add(cert).ok(); + } + let tls = Arc::new( + ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth() + ); + HttpClient { + tls, + connect_timeout: self.connect_timeout, + request_timeout: self.request_timeout, + } + } +} + +/// Trait alias for streams that work with hyper's IO adapter. +trait IoStream: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Unpin + 'static {} +impl IoStream for T {} diff --git a/src/agent/api/mod.rs b/src/agent/api/mod.rs index 8503dd5..b67307e 100644 --- a/src/agent/api/mod.rs +++ b/src/agent/api/mod.rs @@ -6,6 +6,7 @@ // Diagnostics: anomalies always logged to debug panel. // Set POC_DEBUG=1 for verbose per-turn logging. +pub mod http; pub mod parsing; pub mod types; mod openai; @@ -13,9 +14,10 @@ mod openai; pub use types::*; use anyhow::Result; -use reqwest::Client; use std::time::{Duration, Instant}; +use self::http::{HttpClient, HttpResponse}; + use tokio::sync::mpsc; use crate::agent::tools::{self as agent_tools, summarize_args, ActiveToolCall}; @@ -77,7 +79,7 @@ pub enum StreamEvent { #[derive(Clone)] pub struct ApiClient { - client: Client, + client: HttpClient, api_key: String, pub model: String, base_url: String, @@ -85,11 +87,10 @@ pub struct ApiClient { impl ApiClient { pub fn new(base_url: &str, api_key: &str, model: &str) -> Self { - let client = Client::builder() + let client = HttpClient::builder() .connect_timeout(Duration::from_secs(30)) .timeout(Duration::from_secs(600)) - .build() - .expect("failed to build HTTP client"); + .build(); Self { client, @@ -198,14 +199,14 @@ impl ApiClient { /// Send an HTTP request and check for errors. Shared by both backends. pub(crate) async fn send_and_check( - client: &Client, + client: &HttpClient, url: &str, body: &impl serde::Serialize, auth_header: (&str, &str), extra_headers: &[(&str, &str)], debug_label: &str, request_json: Option<&str>, -) -> Result { +) -> Result { let debug = std::env::var("POC_DEBUG").is_ok(); let start = Instant::now(); @@ -219,49 +220,36 @@ pub(crate) async fn send_and_check( ); } - let mut req = client - .post(url) - .header(auth_header.0, auth_header.1) - .header("Content-Type", "application/json"); + let mut headers: Vec<(&str, &str)> = Vec::with_capacity(extra_headers.len() + 1); + headers.push(auth_header); + headers.extend_from_slice(extra_headers); - for (name, value) in extra_headers { - req = req.header(*name, *value); - } - - let response = req - .json(body) - .send() + let response = client + .send_json("POST", url, &headers, body) .await .map_err(|e| { - let cause = if e.is_connect() { + let msg = e.to_string(); + let cause = if msg.contains("connect timeout") || msg.contains("TCP connect") { "connection refused" - } else if e.is_timeout() { + } else if msg.contains("request timeout") { "request timed out" - } else if e.is_request() { - "request error" } else { - "unknown" + "request error" }; - anyhow::anyhow!("{} ({}): {:?}", cause, url, e.without_url()) + anyhow::anyhow!("{} ({}): {}", cause, url, msg) })?; let status = response.status(); let elapsed = start.elapsed(); if debug { - // Log interesting response headers - let headers = response.headers(); for name in [ "x-ratelimit-remaining", "x-ratelimit-limit", "x-request-id", ] { - if let Some(val) = headers.get(name) { - dbglog!( - "header {}: {}", - name, - val.to_str().unwrap_or("?") - ); + if let Some(val) = response.header(name) { + dbglog!("header {}: {}", name, val); } } } @@ -357,7 +345,7 @@ impl SseReader { /// Ok(None) when the stream ends or [DONE] is received. pub(crate) async fn next_event( &mut self, - response: &mut reqwest::Response, + response: &mut HttpResponse, ) -> Result> { loop { // Drain complete lines from the buffer before reading more chunks diff --git a/src/agent/api/openai.rs b/src/agent/api/openai.rs index 7449ea8..7d58370 100644 --- a/src/agent/api/openai.rs +++ b/src/agent/api/openai.rs @@ -5,9 +5,9 @@ // Also used for local models (Qwen, llama) via compatible servers. use anyhow::Result; -use reqwest::Client; use tokio::sync::mpsc; +use super::http::HttpClient; use super::types::*; use super::StreamEvent; @@ -15,7 +15,7 @@ use super::StreamEvent; /// parsed StreamEvents through the channel. The caller (runner) /// handles routing to the UI. pub(super) async fn stream_events( - client: &Client, + client: &HttpClient, base_url: &str, api_key: &str, model: &str, diff --git a/src/agent/tools/web.rs b/src/agent/tools/web.rs index 8dce8b3..9861d81 100644 --- a/src/agent/tools/web.rs +++ b/src/agent/tools/web.rs @@ -27,11 +27,10 @@ async fn web_fetch(args: &serde_json::Value) -> Result { let a: FetchArgs = serde_json::from_value(args.clone()) .context("invalid web_fetch arguments")?; - let client = http_client()?; - let response = client.get(&a.url) - .header("User-Agent", "consciousness/0.3") - .send() - .await + let client = http_client(); + let response = client.get_with_headers(&a.url, &[ + ("user-agent", "consciousness/0.3"), + ]).await .with_context(|| format!("failed to fetch {}", a.url))?; let status = response.status(); @@ -61,7 +60,7 @@ async fn web_search(args: &serde_json::Value) -> Result { .context("invalid web_search arguments")?; // Use DuckDuckGo HTML search — no API key needed - let client = http_client()?; + let client = http_client(); let encoded: String = a.query.chars().map(|c| { if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' { c.to_string() @@ -72,10 +71,9 @@ async fn web_search(args: &serde_json::Value) -> Result { } }).collect(); let url = format!("https://html.duckduckgo.com/html/?q={}", encoded); - let response = client.get(&url) - .header("User-Agent", "consciousness/0.3") - .send() - .await + let response = client.get_with_headers(&url, &[ + ("user-agent", "consciousness/0.3"), + ]).await .context("search request failed")?; let body = response.text().await @@ -86,20 +84,16 @@ async fn web_search(args: &serde_json::Value) -> Result { for chunk in body.split("class=\"result__body\"") { if results.len() >= a.num_results { break; } if results.is_empty() && !chunk.contains("result__title") { - // Skip the first split (before any results) continue; } - // Extract title let title = extract_between(chunk, "class=\"result__a\"", "") .map(strip_tags) .unwrap_or_default(); - // Extract URL let href = extract_between(chunk, "href=\"", "\"") .unwrap_or_default(); - // Extract snippet let snippet = extract_between(chunk, "class=\"result__snippet\"", "") .map(strip_tags) .unwrap_or_default(); @@ -118,30 +112,37 @@ async fn web_search(args: &serde_json::Value) -> Result { // ── Helpers ───────────────────────────────────────────────────── -fn http_client() -> Result { - reqwest::Client::builder() +fn http_client() -> crate::agent::api::http::HttpClient { + crate::agent::api::http::HttpClient::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .context("failed to build HTTP client") } fn extract_between<'a>(text: &'a str, start: &str, end: &str) -> Option<&'a str> { let start_idx = text.find(start)? + start.len(); // Skip past the closing > of the start tag let rest = &text[start_idx..]; - let tag_end = rest.find('>')?; - let rest = &rest[tag_end + 1..]; - let end_idx = rest.find(end)?; - Some(&rest[..end_idx]) + let gt = rest.find('>')?; + let content_start = start_idx + gt + 1; + let content = &text[content_start..]; + let end_idx = content.find(end)?; + Some(&content[..end_idx]) } -fn strip_tags(s: &str) -> String { +fn strip_tags(html: &str) -> String { let mut out = String::new(); let mut in_tag = false; - for ch in s.chars() { - if ch == '<' { in_tag = true; } - else if ch == '>' { in_tag = false; } - else if !in_tag { out.push(ch); } + for ch in html.chars() { + match ch { + '<' => in_tag = true, + '>' => in_tag = false, + _ if !in_tag => out.push(ch), + _ => {} + } } - out + out.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace(""", "\"") + .replace("'", "'") } diff --git a/src/subconscious/learn.rs b/src/subconscious/learn.rs index ebb24a3..7e2722f 100644 --- a/src/subconscious/learn.rs +++ b/src/subconscious/learn.rs @@ -76,35 +76,29 @@ struct ScoreResponse { scores: Vec, } -fn http_client() -> reqwest::Client { - reqwest::Client::builder() +fn http_client() -> crate::agent::api::http::HttpClient { + crate::agent::api::http::HttpClient::builder() .timeout(SCORE_TIMEOUT) - .pool_max_idle_per_host(2) .build() - .unwrap_or_default() } async fn call_score( - http: &reqwest::Client, + http: &crate::agent::api::http::HttpClient, client: &ApiClient, messages: &[serde_json::Value], ) -> anyhow::Result> { + let url = format!("{}/score", client.base_url()); + let auth = format!("Bearer {}", client.api_key()); + let body = serde_json::json!({ + "model": client.model, + "messages": messages, + "logprobs": 1, + }); let response = http - .post(format!("{}/score", client.base_url())) - .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", client.api_key())) - .json(&serde_json::json!({ - "model": client.model, - "messages": messages, - "logprobs": 1, - })) - .send() - .await - .map_err(|e| if e.is_timeout() { - anyhow::anyhow!("score request timed out after {}s", SCORE_TIMEOUT.as_secs()) - } else { - anyhow::anyhow!("score request failed: {}", e) - })?; + .send_json("POST", &url, &[ + ("authorization", &auth), + ], &body) + .await?; let status = response.status(); let body: serde_json::Value = response.json().await?; @@ -135,7 +129,7 @@ fn divergence(baseline: &[ScoreResult], without: &[ScoreResult]) -> Vec { /// Score two message sets and return total divergence. async fn score_divergence( - http: &reqwest::Client, + http: &crate::agent::api::http::HttpClient, client: &ApiClient, context: &ContextState, range: std::ops::Range,