Revert http client changes (#18892)

Mikayla Maki created

These proved to be too unstable. Will restore these changes once the issues have been fixed.

Release Notes:

- N/A

Change summary

Cargo.lock                                        | 518 ++++++----------
Cargo.toml                                        |  15 
crates/client/Cargo.toml                          |   3 
crates/client/src/client.rs                       |  27 
crates/collab/Cargo.toml                          |   2 
crates/collab/src/llm.rs                          |  12 
crates/collab/src/rpc.rs                          |   6 
crates/evals/Cargo.toml                           |   2 
crates/evals/src/eval.rs                          |   7 
crates/extension/Cargo.toml                       |   3 
crates/extension/src/extension_builder.rs         |   2 
crates/extension/src/extension_store_test.rs      |  52 -
crates/extension_cli/Cargo.toml                   |   2 
crates/extension_cli/src/main.rs                  |   9 
crates/gpui/src/app.rs                            |   4 
crates/http_client/Cargo.toml                     |   4 
crates/http_client/src/http_client.rs             |  56 -
crates/isahc_http_client/Cargo.toml               |  15 
crates/isahc_http_client/LICENSE-APACHE           |   1 
crates/isahc_http_client/src/isahc_http_client.rs | 105 +++
crates/live_kit_server/Cargo.toml                 |   2 
crates/reqwest_client/Cargo.toml                  |  31 -
crates/reqwest_client/LICENSE-GPL                 |   1 
crates/reqwest_client/examples/client.rs          |  16 
crates/reqwest_client/src/reqwest_client.rs       | 259 --------
crates/semantic_index/Cargo.toml                  |   2 
crates/semantic_index/examples/index.rs           |   7 
crates/storybook/Cargo.toml                       |   2 
crates/storybook/src/storybook.rs                 |  12 
crates/ureq_client/LICENSE-GPL                    |   1 
crates/ureq_client/examples/client.rs             |  24 
crates/ureq_client/src/ureq_client.rs             | 200 ------
crates/vim/Cargo.toml                             |   2 
crates/zed/Cargo.toml                             |   2 
crates/zed/src/main.rs                            |  10 
35 files changed, 372 insertions(+), 1,044 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -846,8 +846,8 @@ dependencies = [
  "chrono",
  "futures-util",
  "http-types",
- "hyper 0.14.30",
- "hyper-rustls 0.24.2",
+ "hyper",
+ "hyper-rustls",
  "serde",
  "serde_json",
  "serde_path_to_error",
@@ -880,14 +880,15 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
 
 [[package]]
 name = "async-tls"
-version = "0.13.0"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e"
+checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795"
 dependencies = [
  "futures-core",
  "futures-io",
- "rustls 0.21.12",
+ "rustls 0.20.9",
  "rustls-pemfile 1.0.4",
+ "webpki",
  "webpki-roots 0.22.6",
 ]
 
@@ -904,9 +905,9 @@ dependencies = [
 
 [[package]]
 name = "async-tungstenite"
-version = "0.28.0"
+version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b"
+checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc"
 dependencies = [
  "async-std",
  "async-tls",
@@ -914,7 +915,7 @@ dependencies = [
  "futures-util",
  "log",
  "pin-project-lite",
- "tungstenite 0.24.0",
+ "tungstenite 0.20.1",
 ]
 
 [[package]]
@@ -1063,7 +1064,7 @@ dependencies = [
  "fastrand 2.1.1",
  "hex",
  "http 0.2.12",
- "ring",
+ "ring 0.17.8",
  "time",
  "tokio",
  "tracing",
@@ -1232,7 +1233,7 @@ dependencies = [
  "once_cell",
  "p256",
  "percent-encoding",
- "ring",
+ "ring 0.17.8",
  "sha2",
  "subtle",
  "time",
@@ -1335,13 +1336,13 @@ dependencies = [
  "aws-smithy-types",
  "bytes 1.7.1",
  "fastrand 2.1.1",
- "h2 0.3.26",
+ "h2",
  "http 0.2.12",
  "http-body 0.4.6",
  "http-body 1.0.1",
  "httparse",
- "hyper 0.14.30",
- "hyper-rustls 0.24.2",
+ "hyper",
+ "hyper-rustls",
  "once_cell",
  "pin-project-lite",
  "pin-utils",
@@ -1431,7 +1432,7 @@ dependencies = [
  "headers",
  "http 0.2.12",
  "http-body 0.4.6",
- "hyper 0.14.30",
+ "hyper",
  "itoa",
  "matchit",
  "memchr",
@@ -1444,7 +1445,7 @@ dependencies = [
  "serde_path_to_error",
  "serde_urlencoded",
  "sha1",
- "sync_wrapper 0.1.2",
+ "sync_wrapper",
  "tokio",
  "tokio-tungstenite 0.20.1",
  "tower",
@@ -1583,7 +1584,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "rustc-hash 1.1.0",
+ "rustc-hash",
  "shlex",
  "syn 2.0.76",
 ]
@@ -1603,7 +1604,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "rustc-hash 1.1.0",
+ "rustc-hash",
  "shlex",
  "syn 2.0.76",
 ]
@@ -2099,6 +2100,12 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
+[[package]]
+name = "castaway"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
+
 [[package]]
 name = "cbc"
 version = "0.1.2"
@@ -2356,8 +2363,8 @@ dependencies = [
  "clickhouse-derive",
  "clickhouse-rs-cityhash-sys",
  "futures 0.3.30",
- "hyper 0.14.30",
- "hyper-tls 0.5.0",
+ "hyper",
+ "hyper-tls",
  "lz4",
  "sealed",
  "serde",
@@ -2395,7 +2402,6 @@ dependencies = [
  "anyhow",
  "async-native-tls",
  "async-recursion 0.3.2",
- "async-tls",
  "async-tungstenite",
  "chrono",
  "clock",
@@ -2413,6 +2419,8 @@ dependencies = [
  "rand 0.8.5",
  "release_channel",
  "rpc",
+ "rustls 0.20.9",
+ "rustls-native-certs 0.8.0",
  "schemars",
  "serde",
  "serde_json",
@@ -2559,8 +2567,9 @@ dependencies = [
  "headless",
  "hex",
  "http_client",
- "hyper 0.14.30",
+ "hyper",
  "indoc",
+ "isahc_http_client",
  "jsonwebtoken",
  "language",
  "language_model",
@@ -2584,8 +2593,7 @@ dependencies = [
  "release_channel",
  "remote",
  "remote_server",
- "reqwest 0.11.27",
- "reqwest_client",
+ "reqwest",
  "rpc",
  "rustc-demangle",
  "scrypt",
@@ -2669,7 +2677,7 @@ dependencies = [
 name = "collections"
 version = "0.1.0"
 dependencies = [
- "rustc-hash 1.1.0",
+ "rustc-hash",
 ]
 
 [[package]]
@@ -2987,7 +2995,7 @@ dependencies = [
  "log",
  "rangemap",
  "rayon",
- "rustc-hash 1.1.0",
+ "rustc-hash",
  "rustybuzz",
  "self_cell",
  "swash",
@@ -3077,7 +3085,7 @@ dependencies = [
  "hashbrown 0.14.5",
  "log",
  "regalloc2",
- "rustc-hash 1.1.0",
+ "rustc-hash",
  "smallvec",
  "target-lexicon",
 ]
@@ -3333,6 +3341,36 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "curl"
+version = "0.4.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6"
+dependencies = [
+ "curl-sys",
+ "libc",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "socket2 0.5.7",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "curl-sys"
+version = "0.4.74+curl-8.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "cursor-icon"
 version = "1.1.0"
@@ -3994,6 +4032,7 @@ dependencies = [
  "git",
  "gpui",
  "http_client",
+ "isahc_http_client",
  "language",
  "languages",
  "node_runtime",
@@ -4004,7 +4043,6 @@ dependencies = [
  "serde_json",
  "settings",
  "smol",
- "ureq_client",
 ]
 
 [[package]]
@@ -4089,6 +4127,7 @@ dependencies = [
  "gpui",
  "http_client",
  "indexed_docs",
+ "isahc_http_client",
  "language",
  "log",
  "lsp",
@@ -4097,7 +4136,6 @@ dependencies = [
  "paths",
  "project",
  "release_channel",
- "reqwest_client",
  "schemars",
  "semantic_version",
  "serde",
@@ -4110,7 +4148,6 @@ dependencies = [
  "tokio",
  "toml 0.8.19",
  "ui",
- "ureq_client",
  "url",
  "util",
  "wasm-encoder 0.215.0",
@@ -4130,9 +4167,9 @@ dependencies = [
  "env_logger",
  "extension",
  "fs",
+ "isahc_http_client",
  "language",
  "log",
- "reqwest_client",
  "rpc",
  "serde",
  "serde_json",
@@ -4379,7 +4416,7 @@ dependencies = [
  "futures-core",
  "futures-sink",
  "nanorand",
- "spin",
+ "spin 0.9.8",
 ]
 
 [[package]]
@@ -5145,25 +5182,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "h2"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
-dependencies = [
- "atomic-waker",
- "bytes 1.7.1",
- "fnv",
- "futures-core",
- "futures-sink",
- "http 1.1.0",
- "indexmap 2.4.0",
- "slab",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
 [[package]]
 name = "half"
 version = "2.4.1"
@@ -5544,10 +5562,8 @@ dependencies = [
  "anyhow",
  "derive_more",
  "futures 0.3.30",
- "http 1.1.0",
+ "http 0.2.12",
  "log",
- "rustls 0.21.12",
- "rustls-native-certs 0.8.0",
  "serde",
  "serde_json",
  "smol",
@@ -5588,7 +5604,7 @@ dependencies = [
  "futures-channel",
  "futures-core",
  "futures-util",
- "h2 0.3.26",
+ "h2",
  "http 0.2.12",
  "http-body 0.4.6",
  "httparse",
@@ -5602,26 +5618,6 @@ dependencies = [
  "want",
 ]
 
-[[package]]
-name = "hyper"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
-dependencies = [
- "bytes 1.7.1",
- "futures-channel",
- "futures-util",
- "h2 0.4.6",
- "http 1.1.0",
- "http-body 1.0.1",
- "httparse",
- "itoa",
- "pin-project-lite",
- "smallvec",
- "tokio",
- "want",
-]
-
 [[package]]
 name = "hyper-rustls"
 version = "0.24.2"
@@ -5630,29 +5626,12 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
 dependencies = [
  "futures-util",
  "http 0.2.12",
- "hyper 0.14.30",
+ "hyper",
  "log",
  "rustls 0.21.12",
  "rustls-native-certs 0.6.3",
  "tokio",
- "tokio-rustls 0.24.1",
-]
-
-[[package]]
-name = "hyper-rustls"
-version = "0.27.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
-dependencies = [
- "futures-util",
- "http 1.1.0",
- "hyper 1.4.1",
- "hyper-util",
- "rustls 0.23.13",
- "rustls-pki-types",
- "tokio",
- "tokio-rustls 0.26.0",
- "tower-service",
+ "tokio-rustls",
 ]
 
 [[package]]
@@ -5662,47 +5641,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
 dependencies = [
  "bytes 1.7.1",
- "hyper 0.14.30",
+ "hyper",
  "native-tls",
  "tokio",
  "tokio-native-tls",
 ]
 
-[[package]]
-name = "hyper-tls"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
-dependencies = [
- "bytes 1.7.1",
- "http-body-util",
- "hyper 1.4.1",
- "hyper-util",
- "native-tls",
- "tokio",
- "tokio-native-tls",
- "tower-service",
-]
-
-[[package]]
-name = "hyper-util"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
-dependencies = [
- "bytes 1.7.1",
- "futures-channel",
- "futures-util",
- "http 1.1.0",
- "http-body 1.0.1",
- "hyper 1.4.1",
- "pin-project-lite",
- "socket2 0.5.7",
- "tokio",
- "tower-service",
- "tracing",
-]
-
 [[package]]
 name = "iana-time-zone"
 version = "0.1.60"
@@ -6070,6 +6014,44 @@ version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
 
+[[package]]
+name = "isahc"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
+dependencies = [
+ "async-channel 1.9.0",
+ "castaway",
+ "crossbeam-utils",
+ "curl",
+ "curl-sys",
+ "encoding_rs",
+ "event-listener 2.5.3",
+ "futures-lite 1.13.0",
+ "http 0.2.12",
+ "log",
+ "mime",
+ "once_cell",
+ "polling 2.8.0",
+ "slab",
+ "sluice",
+ "tracing",
+ "tracing-futures",
+ "url",
+ "waker-fn",
+]
+
+[[package]]
+name = "isahc_http_client"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "futures 0.3.30",
+ "http_client",
+ "isahc",
+ "util",
+]
+
 [[package]]
 name = "itertools"
 version = "0.10.5"
@@ -6174,7 +6156,7 @@ dependencies = [
  "base64 0.21.7",
  "js-sys",
  "pem",
- "ring",
+ "ring 0.17.8",
  "serde",
  "serde_json",
  "simple_asn1",
@@ -6424,7 +6406,7 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 dependencies = [
- "spin",
+ "spin 0.9.8",
 ]
 
 [[package]]
@@ -6619,7 +6601,7 @@ dependencies = [
  "prost",
  "prost-build",
  "prost-types",
- "reqwest 0.12.8",
+ "reqwest",
  "serde",
 ]
 
@@ -7103,7 +7085,7 @@ dependencies = [
  "hexf-parse",
  "indexmap 2.4.0",
  "log",
- "rustc-hash 1.1.0",
+ "rustc-hash",
  "spirv",
  "termcolor",
  "thiserror",
@@ -8751,54 +8733,6 @@ dependencies = [
  "zed_actions",
 ]
 
-[[package]]
-name = "quinn"
-version = "0.11.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684"
-dependencies = [
- "bytes 1.7.1",
- "pin-project-lite",
- "quinn-proto",
- "quinn-udp",
- "rustc-hash 2.0.0",
- "rustls 0.23.13",
- "socket2 0.5.7",
- "thiserror",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "quinn-proto"
-version = "0.11.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
-dependencies = [
- "bytes 1.7.1",
- "rand 0.8.5",
- "ring",
- "rustc-hash 2.0.0",
- "rustls 0.23.13",
- "slab",
- "thiserror",
- "tinyvec",
- "tracing",
-]
-
-[[package]]
-name = "quinn-udp"
-version = "0.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b"
-dependencies = [
- "libc",
- "once_cell",
- "socket2 0.5.7",
- "tracing",
- "windows-sys 0.59.0",
-]
-
 [[package]]
 name = "quote"
 version = "1.0.37"
@@ -9075,7 +9009,7 @@ checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6"
 dependencies = [
  "hashbrown 0.13.2",
  "log",
- "rustc-hash 1.1.0",
+ "rustc-hash",
  "slice-group-by",
  "smallvec",
 ]
@@ -9254,11 +9188,11 @@ dependencies = [
  "encoding_rs",
  "futures-core",
  "futures-util",
- "h2 0.3.26",
+ "h2",
  "http 0.2.12",
  "http-body 0.4.6",
- "hyper 0.14.30",
- "hyper-tls 0.5.0",
+ "hyper",
+ "hyper-tls",
  "ipnet",
  "js-sys",
  "log",
@@ -9271,8 +9205,8 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_urlencoded",
- "sync_wrapper 0.1.2",
- "system-configuration 0.5.1",
+ "sync_wrapper",
+ "system-configuration",
  "tokio",
  "tokio-native-tls",
  "tower-service",
@@ -9283,68 +9217,6 @@ dependencies = [
  "winreg 0.50.0",
 ]
 
-[[package]]
-name = "reqwest"
-version = "0.12.8"
-source = "git+https://github.com/zed-industries/reqwest.git?rev=fd110f6998da16bbca97b6dddda9be7827c50e29#fd110f6998da16bbca97b6dddda9be7827c50e29"
-dependencies = [
- "base64 0.22.1",
- "bytes 1.7.1",
- "encoding_rs",
- "futures-core",
- "futures-util",
- "h2 0.4.6",
- "http 1.1.0",
- "http-body 1.0.1",
- "http-body-util",
- "hyper 1.4.1",
- "hyper-rustls 0.27.3",
- "hyper-tls 0.6.0",
- "hyper-util",
- "ipnet",
- "js-sys",
- "log",
- "mime",
- "native-tls",
- "once_cell",
- "percent-encoding",
- "pin-project-lite",
- "quinn",
- "rustls 0.23.13",
- "rustls-pemfile 2.1.3",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sync_wrapper 1.0.1",
- "system-configuration 0.6.1",
- "tokio",
- "tokio-native-tls",
- "tokio-rustls 0.26.0",
- "tokio-util",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "wasm-streams",
- "web-sys",
- "windows-registry",
-]
-
-[[package]]
-name = "reqwest_client"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "bytes 1.7.1",
- "futures 0.3.30",
- "http_client",
- "reqwest 0.12.8",
- "serde",
- "smol",
- "tokio",
-]
-
 [[package]]
 name = "resvg"
 version = "0.41.0"
@@ -9393,6 +9265,21 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin 0.5.2",
+ "untrusted 0.7.1",
+ "web-sys",
+ "winapi",
+]
+
 [[package]]
 name = "ring"
 version = "0.17.8"
@@ -9403,8 +9290,8 @@ dependencies = [
  "cfg-if",
  "getrandom 0.2.15",
  "libc",
- "spin",
- "untrusted",
+ "spin 0.9.8",
+ "untrusted 0.9.0",
  "windows-sys 0.52.0",
 ]
 
@@ -9560,7 +9447,7 @@ dependencies = [
  "futures 0.3.30",
  "glob",
  "rand 0.8.5",
- "ring",
+ "ring 0.17.8",
  "serde",
  "serde_json",
  "shellexpand 3.1.0",
@@ -9632,12 +9519,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
-[[package]]
-name = "rustc-hash"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
-
 [[package]]
 name = "rustc_version"
 version = "0.4.1"
@@ -9689,28 +9570,26 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.21.12"
+version = "0.20.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
+checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
 dependencies = [
  "log",
- "ring",
- "rustls-webpki 0.101.7",
+ "ring 0.16.20",
  "sct",
+ "webpki",
 ]
 
 [[package]]
 name = "rustls"
-version = "0.23.13"
+version = "0.21.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
+checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
 dependencies = [
- "once_cell",
- "ring",
- "rustls-pki-types",
- "rustls-webpki 0.102.8",
- "subtle",
- "zeroize",
+ "log",
+ "ring 0.17.8",
+ "rustls-webpki",
+ "sct",
 ]
 
 [[package]]
@@ -9769,19 +9648,8 @@ version = "0.101.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
 dependencies = [
- "ring",
- "untrusted",
-]
-
-[[package]]
-name = "rustls-webpki"
-version = "0.102.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
-dependencies = [
- "ring",
- "rustls-pki-types",
- "untrusted",
+ "ring 0.17.8",
+ "untrusted 0.9.0",
 ]
 
 [[package]]
@@ -9895,8 +9763,8 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
 dependencies = [
- "ring",
- "untrusted",
+ "ring 0.17.8",
+ "untrusted 0.9.0",
 ]
 
 [[package]]
@@ -10092,6 +9960,7 @@ dependencies = [
  "gpui",
  "heed",
  "http_client",
+ "isahc_http_client",
  "language",
  "language_model",
  "languages",
@@ -10109,7 +9978,6 @@ dependencies = [
  "tree-sitter",
  "ui",
  "unindent",
- "ureq_client",
  "util",
  "workspace",
  "worktree",
@@ -10542,6 +10410,17 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "sluice"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
+dependencies = [
+ "async-channel 1.9.0",
+ "futures-core",
+ "futures-io",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.13.2"
@@ -10656,6 +10535,12 @@ dependencies = [
  "smallvec",
 ]
 
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
 [[package]]
 name = "spin"
 version = "0.9.8"
@@ -10986,6 +10871,7 @@ dependencies = [
  "fuzzy",
  "gpui",
  "indoc",
+ "isahc_http_client",
  "language",
  "log",
  "menu",
@@ -10999,7 +10885,6 @@ dependencies = [
  "theme",
  "title_bar",
  "ui",
- "ureq_client",
 ]
 
 [[package]]
@@ -11287,15 +11172,6 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
 
-[[package]]
-name = "sync_wrapper"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
-dependencies = [
- "futures-core",
-]
-
 [[package]]
 name = "synchronoise"
 version = "1.0.1"
@@ -11336,18 +11212,7 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
 dependencies = [
  "bitflags 1.3.2",
  "core-foundation 0.9.4",
- "system-configuration-sys 0.5.0",
-]
-
-[[package]]
-name = "system-configuration"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
-dependencies = [
- "bitflags 2.6.0",
- "core-foundation 0.9.4",
- "system-configuration-sys 0.6.0",
+ "system-configuration-sys",
 ]
 
 [[package]]
@@ -11360,16 +11225,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "system-configuration-sys"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
 [[package]]
 name = "system-deps"
 version = "6.2.2"
@@ -11747,7 +11602,7 @@ dependencies = [
  "fancy-regex",
  "lazy_static",
  "parking_lot",
- "rustc-hash 1.1.0",
+ "rustc-hash",
 ]
 
 [[package]]
@@ -11963,17 +11818,6 @@ dependencies = [
  "tokio",
 ]
 
-[[package]]
-name = "tokio-rustls"
-version = "0.26.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
-dependencies = [
- "rustls 0.23.13",
- "rustls-pki-types",
- "tokio",
-]
-
 [[package]]
 name = "tokio-socks"
 version = "0.5.2"
@@ -12023,9 +11867,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.7.12"
+version = "0.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
+checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
 dependencies = [
  "bytes 1.7.1",
  "futures-core",
@@ -12207,6 +12051,16 @@ dependencies = [
  "valuable",
 ]
 
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project",
+ "tracing",
+]
+
 [[package]]
 name = "tracing-log"
 version = "0.2.0"

Cargo.toml 🔗

@@ -52,6 +52,7 @@ members = [
     "crates/indexed_docs",
     "crates/inline_completion_button",
     "crates/install_cli",
+    "crates/isahc_http_client",
     "crates/journal",
     "crates/language",
     "crates/language_model",
@@ -87,7 +88,6 @@ members = [
     "crates/remote",
     "crates/remote_server",
     "crates/repl",
-    "crates/reqwest_client",
     "crates/rich_text",
     "crates/rope",
     "crates/rpc",
@@ -122,7 +122,6 @@ members = [
     "crates/ui",
     "crates/ui_input",
     "crates/ui_macros",
-    "crates/ureq_client",
     "crates/util",
     "crates/vcs_menu",
     "crates/vim",
@@ -230,6 +229,7 @@ image_viewer = { path = "crates/image_viewer" }
 indexed_docs = { path = "crates/indexed_docs" }
 inline_completion_button = { path = "crates/inline_completion_button" }
 install_cli = { path = "crates/install_cli" }
+isahc_http_client = { path = "crates/isahc_http_client" }
 journal = { path = "crates/journal" }
 language = { path = "crates/language" }
 language_model = { path = "crates/language_model" }
@@ -266,7 +266,6 @@ release_channel = { path = "crates/release_channel" }
 remote = { path = "crates/remote" }
 remote_server = { path = "crates/remote_server" }
 repl = { path = "crates/repl" }
-reqwest_client = { path = "crates/reqwest_client" }
 rich_text = { path = "crates/rich_text" }
 rope = { path = "crates/rope" }
 rpc = { path = "crates/rpc" }
@@ -301,7 +300,6 @@ title_bar = { path = "crates/title_bar" }
 ui = { path = "crates/ui" }
 ui_input = { path = "crates/ui_input" }
 ui_macros = { path = "crates/ui_macros" }
-ureq_client = { path = "crates/ureq_client" }
 util = { path = "crates/util" }
 vcs_menu = { path = "crates/vcs_menu" }
 vim = { path = "crates/vim" }
@@ -329,7 +327,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
 async-recursion = "1.0.0"
 async-tar = "0.5.0"
 async-trait = "0.1"
-async-tungstenite = "0.28"
+async-tungstenite = "0.23"
 async-watch = "0.3.1"
 async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
 base64 = "0.22"
@@ -369,6 +367,10 @@ ignore = "0.4.22"
 image = "0.25.1"
 indexmap = { version = "1.6.2", features = ["serde"] }
 indoc = "2"
+# We explicitly disable http2 support in isahc.
+isahc = { version = "1.7.2", default-features = false, features = [
+    "text-decoding",
+] }
 itertools = "0.13.0"
 jsonwebtoken = "9.3"
 libc = "0.2"
@@ -393,14 +395,13 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
 rand = "0.8.5"
 regex = "1.5"
 repair_json = "0.1.0"
-reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29" }
 rsa = "0.9.6"
 runtimelib = { version = "0.15", default-features = false, features = [
     "async-dispatcher-runtime",
 ] }
 rustc-demangle = "0.1.23"
 rust-embed = { version = "8.4", features = ["include-exclude"] }
-rustls = "0.21.12"
+rustls = "0.20.3"
 rustls-native-certs = "0.8.0"
 schemars = { version = "0.8", features = ["impl_json_schema"] }
 semver = "1.0"

crates/client/Cargo.toml 🔗

@@ -18,7 +18,6 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
 [dependencies]
 anyhow.workspace = true
 async-recursion = "0.3"
-async-tls = "0.13"
 async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
 chrono = { workspace = true, features = ["serde"] }
 clock.workspace = true
@@ -35,6 +34,8 @@ postage.workspace = true
 rand.workspace = true
 release_channel.workspace = true
 rpc = { workspace = true, features = ["gpui"] }
+rustls.workspace = true
+rustls-native-certs.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/client/src/client.rs 🔗

@@ -1023,7 +1023,7 @@ impl Client {
         &self,
         http: Arc<HttpClientWithUrl>,
         release_channel: Option<ReleaseChannel>,
-    ) -> impl Future<Output = Result<url::Url>> {
+    ) -> impl Future<Output = Result<Url>> {
         #[cfg(any(test, feature = "test-support"))]
         let url_override = self.rpc_url.read().clone();
 
@@ -1117,7 +1117,7 @@ impl Client {
             // for us from the RPC URL.
             //
             // Among other things, it will generate and set a `Sec-WebSocket-Key` header for us.
-            let mut request = IntoClientRequest::into_client_request(rpc_url.as_str())?;
+            let mut request = rpc_url.into_client_request()?;
 
             // We then modify the request to add our desired headers.
             let request_headers = request.headers_mut();
@@ -1137,13 +1137,30 @@ impl Client {
 
             match url_scheme {
                 Https => {
+                    let client_config = {
+                        let mut root_store = rustls::RootCertStore::empty();
+
+                        let root_certs = rustls_native_certs::load_native_certs();
+                        for error in root_certs.errors {
+                            log::warn!("error loading native certs: {:?}", error);
+                        }
+                        root_store.add_parsable_certificates(
+                            &root_certs
+                                .certs
+                                .into_iter()
+                                .map(|cert| cert.as_ref().to_owned())
+                                .collect::<Vec<_>>(),
+                        );
+                        rustls::ClientConfig::builder()
+                            .with_safe_defaults()
+                            .with_root_certificates(root_store)
+                            .with_no_client_auth()
+                    };
                     let (stream, _) =
                         async_tungstenite::async_tls::client_async_tls_with_connector(
                             request,
                             stream,
-                            Some(async_tls::TlsConnector::from(
-                                http_client::TLS_CONFIG.clone(),
-                            )),
+                            Some(client_config.into()),
                         )
                         .await?;
                     Ok(Connection::new(

crates/collab/Cargo.toml 🔗

@@ -37,6 +37,7 @@ futures.workspace = true
 google_ai.workspace = true
 hex.workspace = true
 http_client.workspace = true
+isahc_http_client.workspace = true
 jsonwebtoken.workspace = true
 live_kit_server.workspace = true
 log.workspace = true
@@ -47,7 +48,6 @@ prometheus = "0.13"
 prost.workspace = true
 rand.workspace = true
 reqwest = { version = "0.11", features = ["json"] }
-reqwest_client.workspace = true
 rpc.workspace = true
 rustc-demangle.workspace = true
 scrypt = "0.11"

crates/collab/src/llm.rs 🔗

@@ -22,8 +22,7 @@ use chrono::{DateTime, Duration, Utc};
 use collections::HashMap;
 use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
 use futures::{Stream, StreamExt as _};
-
-use reqwest_client::ReqwestClient;
+use isahc_http_client::IsahcHttpClient;
 use rpc::ListModelsResponse;
 use rpc::{
     proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
@@ -44,7 +43,7 @@ pub struct LlmState {
     pub config: Config,
     pub executor: Executor,
     pub db: Arc<LlmDatabase>,
-    pub http_client: ReqwestClient,
+    pub http_client: IsahcHttpClient,
     pub clickhouse_client: Option<clickhouse::Client>,
     active_user_count_by_model:
         RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
@@ -70,8 +69,11 @@ impl LlmState {
         let db = Arc::new(db);
 
         let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
-        let http_client =
-            ReqwestClient::user_agent(&user_agent).context("failed to construct http client")?;
+        let http_client = IsahcHttpClient::builder()
+            .default_header("User-Agent", user_agent)
+            .build()
+            .map(IsahcHttpClient::from)
+            .context("failed to construct http client")?;
 
         let this = Self {
             executor,

crates/collab/src/rpc.rs 🔗

@@ -36,8 +36,8 @@ use collections::{HashMap, HashSet};
 pub use connection_pool::{ConnectionPool, ZedVersion};
 use core::fmt::{self, Debug, Formatter};
 use http_client::HttpClient;
+use isahc_http_client::IsahcHttpClient;
 use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL};
-use reqwest_client::ReqwestClient;
 use sha2::Digest;
 use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
 
@@ -964,8 +964,8 @@ impl Server {
             tracing::info!("connection opened");
 
             let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
-            let http_client = match ReqwestClient::user_agent(&user_agent) {
-                Ok(http_client) => Arc::new(http_client),
+            let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() {
+                Ok(http_client) => Arc::new(IsahcHttpClient::from(http_client)),
                 Err(error) => {
                     tracing::error!(?error, "failed to create HTTP client");
                     return;

crates/evals/Cargo.toml 🔗

@@ -25,6 +25,7 @@ fs.workspace = true
 git.workspace = true
 gpui.workspace = true
 http_client.workspace = true
+isahc_http_client.workspace = true
 language.workspace = true
 languages.workspace = true
 node_runtime.workspace = true
@@ -35,4 +36,3 @@ serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true
 smol.workspace = true
-ureq_client.workspace = true

crates/evals/src/eval.rs 🔗

@@ -32,7 +32,6 @@ use std::{
         Arc,
     },
 };
-use ureq_client::UreqClient;
 
 const CODESEARCH_NET_DIR: &'static str = "target/datasets/code-search-net";
 const EVAL_REPOS_DIR: &'static str = "target/datasets/eval-repos";
@@ -101,11 +100,7 @@ fn main() -> Result<()> {
 
     gpui::App::headless().run(move |cx| {
         let executor = cx.background_executor().clone();
-        let client = Arc::new(UreqClient::new(
-            None,
-            "Zed LLM evals".to_string(),
-            executor.clone(),
-        ));
+        let client = isahc_http_client::IsahcHttpClient::new(None, None);
         cx.set_http_client(client.clone());
         match cli.command {
             Commands::Fetch {} => {

crates/extension/Cargo.toml 🔗

@@ -56,6 +56,7 @@ wit-component.workspace = true
 workspace.workspace = true
 
 [dev-dependencies]
+isahc_http_client.workspace = true
 ctor.workspace = true
 env_logger.workspace = true
 fs = { workspace = true, features = ["test-support"] }
@@ -63,7 +64,5 @@ gpui = { workspace = true, features = ["test-support"] }
 language = { workspace = true, features = ["test-support"] }
 parking_lot.workspace = true
 project = { workspace = true, features = ["test-support"] }
-reqwest_client.workspace = true
 tokio.workspace = true
-ureq_client.workspace = true
 workspace = { workspace = true, features = ["test-support"] }

crates/extension/src/extension_builder.rs 🔗

@@ -25,7 +25,7 @@ use wit_component::ComponentEncoder;
 /// Once Rust 1.78 is released, there will be a `wasm32-wasip2` target available, so we will
 /// not need the adapter anymore.
 const RUST_TARGET: &str = "wasm32-wasip1";
-pub const WASI_ADAPTER_URL: &str =
+const WASI_ADAPTER_URL: &str =
     "https://github.com/bytecodealliance/wasmtime/releases/download/v18.0.2/wasi_snapshot_preview1.reactor.wasm";
 
 /// Compiling Tree-sitter parsers from C to WASM requires Clang 17, and a WASM build of libc

crates/extension/src/extension_store_test.rs 🔗

@@ -1,4 +1,3 @@
-use crate::extension_builder::WASI_ADAPTER_URL;
 use crate::extension_manifest::SchemaVersion;
 use crate::extension_settings::ExtensionSettings;
 use crate::{
@@ -12,14 +11,14 @@ use collections::BTreeMap;
 use fs::{FakeFs, Fs, RealFs};
 use futures::{io::BufReader, AsyncReadExt, StreamExt};
 use gpui::{Context, SemanticVersion, TestAppContext};
-use http_client::{AsyncBody, FakeHttpClient, HttpClient, Response};
+use http_client::{FakeHttpClient, Response};
 use indexed_docs::IndexedDocsRegistry;
+use isahc_http_client::IsahcHttpClient;
 use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use project::{Project, DEFAULT_COMPLETION_CONTEXT};
 use release_channel::AppVersion;
-use reqwest_client::ReqwestClient;
 use serde_json::json;
 use settings::{Settings as _, SettingsStore};
 use snippet_provider::SnippetRegistry;
@@ -29,7 +28,6 @@ use std::{
     sync::Arc,
 };
 use theme::ThemeRegistry;
-use ureq_client::UreqClient;
 use util::test::temp_tree;
 
 #[cfg(test)]
@@ -578,7 +576,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
             std::env::consts::ARCH
         )
     });
-    let builder_client = Arc::new(UreqClient::new(None, user_agent, cx.executor().clone()));
+    let builder_client = IsahcHttpClient::new(None, Some(user_agent));
 
     let extension_store = cx.new_model(|cx| {
         ExtensionStore::new(
@@ -771,50 +769,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
     assert!(fs.metadata(&expected_server_path).await.unwrap().is_none());
 }
 
-#[gpui::test]
-async fn test_wasi_adapter_download(cx: &mut TestAppContext) {
-    let client = Arc::new(UreqClient::new(
-        None,
-        "zed-test-wasi-adapter-download".to_string(),
-        cx.executor().clone(),
-    ));
-
-    let mut response = client
-        .get(WASI_ADAPTER_URL, AsyncBody::default(), true)
-        .await
-        .unwrap();
-
-    let mut content = Vec::new();
-    let mut body = BufReader::new(response.body_mut());
-    body.read_to_end(&mut content).await.unwrap();
-
-    assert!(wasmparser::Parser::is_core_wasm(&content));
-    assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
-    wit_component::ComponentEncoder::default()
-        .adapter("wasi_snapshot_preview1", &content)
-        .unwrap();
-}
-
-#[tokio::test]
-async fn test_wasi_adapter_download_tokio() {
-    let client = Arc::new(ReqwestClient::new());
-
-    let mut response = client
-        .get(WASI_ADAPTER_URL, AsyncBody::default(), true)
-        .await
-        .unwrap();
-
-    let mut content = Vec::new();
-    let mut body = BufReader::new(response.body_mut());
-    body.read_to_end(&mut content).await.unwrap();
-
-    assert!(wasmparser::Parser::is_core_wasm(&content));
-    assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
-    wit_component::ComponentEncoder::default()
-        .adapter("wasi_snapshot_preview1", &content)
-        .unwrap();
-}
-
 fn init_test(cx: &mut TestAppContext) {
     cx.update(|cx| {
         let store = SettingsStore::test(cx);

crates/extension_cli/Cargo.toml 🔗

@@ -18,9 +18,9 @@ clap = { workspace = true, features = ["derive"] }
 env_logger.workspace = true
 extension = { workspace = true, features = ["no-webrtc"] }
 fs.workspace = true
+isahc_http_client.workspace = true
 language.workspace = true
 log.workspace = true
-reqwest_client.workspace = true
 rpc.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/extension_cli/src/main.rs 🔗

@@ -13,8 +13,8 @@ use extension::{
     extension_builder::{CompileExtensionOptions, ExtensionBuilder},
     ExtensionManifest,
 };
+use isahc_http_client::IsahcHttpClient;
 use language::LanguageConfig;
-use reqwest_client::ReqwestClient;
 use theme::ThemeRegistry;
 use tree_sitter::{Language, Query, WasmStore};
 
@@ -66,7 +66,12 @@ async fn main() -> Result<()> {
         std::env::consts::OS,
         std::env::consts::ARCH
     );
-    let http_client = Arc::new(ReqwestClient::user_agent(&user_agent)?);
+    let http_client = Arc::new(
+        IsahcHttpClient::builder()
+            .default_header("User-Agent", user_agent)
+            .build()
+            .map(IsahcHttpClient::from)?,
+    );
 
     let builder = ExtensionBuilder::new(http_client, scratch_dir);
     builder

crates/gpui/src/app.rs 🔗

@@ -1533,8 +1533,4 @@ impl HttpClient for NullHttpClient {
     fn proxy(&self) -> Option<&http_client::Uri> {
         None
     }
-
-    fn type_name(&self) -> &'static str {
-        type_name::<Self>()
-    }
 }

crates/http_client/Cargo.toml 🔗

@@ -16,13 +16,11 @@ path = "src/http_client.rs"
 doctest = true
 
 [dependencies]
+http = "0.2"
 anyhow.workspace = true
 derive_more.workspace = true
 futures.workspace = true
-http = "1.1"
 log.workspace = true
-rustls-native-certs.workspace = true
-rustls.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 smol.workspace = true

crates/http_client/src/http_client.rs 🔗

@@ -11,22 +11,13 @@ use http::request::Builder;
 #[cfg(feature = "test-support")]
 use std::fmt;
 use std::{
-    any::type_name,
-    sync::{Arc, LazyLock, Mutex},
+    sync::{Arc, Mutex},
     time::Duration,
 };
 pub use url::Url;
 
-#[derive(Clone)]
 pub struct ReadTimeout(pub Duration);
-impl Default for ReadTimeout {
-    fn default() -> Self {
-        Self(Duration::from_secs(5))
-    }
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
-
+#[derive(Default, Debug, Clone)]
 pub enum RedirectPolicy {
     #[default]
     NoFollow,
@@ -35,23 +26,6 @@ pub enum RedirectPolicy {
 }
 pub struct FollowRedirects(pub bool);
 
-pub static TLS_CONFIG: LazyLock<Arc<rustls::ClientConfig>> = LazyLock::new(|| {
-    let mut root_store = rustls::RootCertStore::empty();
-
-    let root_certs = rustls_native_certs::load_native_certs();
-    for error in root_certs.errors {
-        log::warn!("error loading native certs: {:?}", error);
-    }
-    root_store.add_parsable_certificates(&root_certs.certs);
-
-    Arc::new(
-        rustls::ClientConfig::builder()
-            .with_safe_defaults()
-            .with_root_certificates(root_store)
-            .with_no_client_auth(),
-    )
-});
-
 pub trait HttpRequestExt {
     /// Set a read timeout on the request.
     /// For isahc, this is the low_speed_timeout.
@@ -73,8 +47,6 @@ impl HttpRequestExt for http::request::Builder {
 }
 
 pub trait HttpClient: 'static + Send + Sync {
-    fn type_name(&self) -> &'static str;
-
     fn send(
         &self,
         req: http::Request<AsyncBody>,
@@ -157,10 +129,6 @@ impl HttpClient for HttpClientWithProxy {
     fn proxy(&self) -> Option<&Uri> {
         self.proxy.as_ref()
     }
-
-    fn type_name(&self) -> &'static str {
-        self.client.type_name()
-    }
 }
 
 impl HttpClient for Arc<HttpClientWithProxy> {
@@ -174,10 +142,6 @@ impl HttpClient for Arc<HttpClientWithProxy> {
     fn proxy(&self) -> Option<&Uri> {
         self.proxy.as_ref()
     }
-
-    fn type_name(&self) -> &'static str {
-        self.client.type_name()
-    }
 }
 
 /// An [`HttpClient`] that has a base URL.
@@ -289,10 +253,6 @@ impl HttpClient for Arc<HttpClientWithUrl> {
     fn proxy(&self) -> Option<&Uri> {
         self.client.proxy.as_ref()
     }
-
-    fn type_name(&self) -> &'static str {
-        self.client.type_name()
-    }
 }
 
 impl HttpClient for HttpClientWithUrl {
@@ -306,10 +266,6 @@ impl HttpClient for HttpClientWithUrl {
     fn proxy(&self) -> Option<&Uri> {
         self.client.proxy.as_ref()
     }
-
-    fn type_name(&self) -> &'static str {
-        self.client.type_name()
-    }
 }
 
 pub fn read_proxy_from_env() -> Option<Uri> {
@@ -350,10 +306,6 @@ impl HttpClient for BlockedHttpClient {
     fn proxy(&self) -> Option<&Uri> {
         None
     }
-
-    fn type_name(&self) -> &'static str {
-        type_name::<Self>()
-    }
 }
 
 #[cfg(feature = "test-support")]
@@ -426,8 +378,4 @@ impl HttpClient for FakeHttpClient {
     fn proxy(&self) -> Option<&Uri> {
         None
     }
-
-    fn type_name(&self) -> &'static str {
-        type_name::<Self>()
-    }
 }

crates/ureq_client/Cargo.toml → crates/isahc_http_client/Cargo.toml 🔗

@@ -1,5 +1,5 @@
 [package]
-name = "ureq_client"
+name = "isahc_http_client"
 version = "0.1.0"
 edition = "2021"
 publish = false
@@ -12,20 +12,11 @@ workspace = true
 test-support = []
 
 [lib]
-path = "src/ureq_client.rs"
-doctest = true
-
-[[example]]
-name = "client"
-path = "examples/client.rs"
+path = "src/isahc_http_client.rs"
 
 [dependencies]
 anyhow.workspace = true
 futures.workspace = true
-gpui.workspace = true
 http_client.workspace = true
-parking_lot.workspace = true
-serde.workspace = true
-smol.workspace = true
-ureq = "=2.9.1"
+isahc.workspace = true
 util.workspace = true

crates/isahc_http_client/src/isahc_http_client.rs 🔗

@@ -0,0 +1,105 @@
+use std::{mem, sync::Arc, time::Duration};
+
+use futures::future::BoxFuture;
+use util::maybe;
+
+pub use isahc::config::Configurable;
+pub struct IsahcHttpClient(isahc::HttpClient);
+
+pub use http_client::*;
+
+impl IsahcHttpClient {
+    pub fn new(proxy: Option<Uri>, user_agent: Option<String>) -> Arc<IsahcHttpClient> {
+        let mut builder = isahc::HttpClient::builder()
+            .connect_timeout(Duration::from_secs(5))
+            .low_speed_timeout(100, Duration::from_secs(5))
+            .proxy(proxy.clone());
+        if let Some(agent) = user_agent {
+            builder = builder.default_header("User-Agent", agent);
+        }
+        Arc::new(IsahcHttpClient(builder.build().unwrap()))
+    }
+    pub fn builder() -> isahc::HttpClientBuilder {
+        isahc::HttpClientBuilder::new()
+    }
+}
+
+impl From<isahc::HttpClient> for IsahcHttpClient {
+    fn from(client: isahc::HttpClient) -> Self {
+        Self(client)
+    }
+}
+
+impl HttpClient for IsahcHttpClient {
+    fn proxy(&self) -> Option<&Uri> {
+        None
+    }
+
+    fn send(
+        &self,
+        req: http_client::http::Request<http_client::AsyncBody>,
+    ) -> BoxFuture<'static, Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>>
+    {
+        let redirect_policy = req
+            .extensions()
+            .get::<http_client::RedirectPolicy>()
+            .cloned()
+            .unwrap_or_default();
+        let read_timeout = req
+            .extensions()
+            .get::<http_client::ReadTimeout>()
+            .map(|t| t.0);
+        let req = maybe!({
+            let (mut parts, body) = req.into_parts();
+            let mut builder = isahc::Request::builder()
+                .method(parts.method)
+                .uri(parts.uri)
+                .version(parts.version);
+            if let Some(read_timeout) = read_timeout {
+                builder = builder.low_speed_timeout(100, read_timeout);
+            }
+
+            let headers = builder.headers_mut()?;
+            mem::swap(headers, &mut parts.headers);
+
+            let extensions = builder.extensions_mut()?;
+            mem::swap(extensions, &mut parts.extensions);
+
+            let isahc_body = match body.0 {
+                http_client::Inner::Empty => isahc::AsyncBody::empty(),
+                http_client::Inner::AsyncReader(reader) => isahc::AsyncBody::from_reader(reader),
+                http_client::Inner::SyncReader(reader) => {
+                    isahc::AsyncBody::from_bytes_static(reader.into_inner())
+                }
+            };
+
+            builder
+                .redirect_policy(match redirect_policy {
+                    http_client::RedirectPolicy::FollowAll => isahc::config::RedirectPolicy::Follow,
+                    http_client::RedirectPolicy::FollowLimit(limit) => {
+                        isahc::config::RedirectPolicy::Limit(limit)
+                    }
+                    http_client::RedirectPolicy::NoFollow => isahc::config::RedirectPolicy::None,
+                })
+                .body(isahc_body)
+                .ok()
+        });
+
+        let client = self.0.clone();
+
+        Box::pin(async move {
+            match req {
+                Some(req) => client
+                    .send_async(req)
+                    .await
+                    .map_err(Into::into)
+                    .map(|response| {
+                        let (parts, body) = response.into_parts();
+                        let body = http_client::AsyncBody::from_reader(body);
+                        http_client::Response::from_parts(parts, body)
+                    }),
+                None => Err(anyhow::anyhow!("Request was malformed")),
+            }
+        })
+    }
+}

crates/live_kit_server/Cargo.toml 🔗

@@ -20,7 +20,7 @@ jsonwebtoken.workspace = true
 log.workspace = true
 prost.workspace = true
 prost-types.workspace = true
-reqwest.workspace = true
+reqwest = "0.11"
 serde.workspace = true
 
 [build-dependencies]

crates/reqwest_client/Cargo.toml 🔗

@@ -1,31 +0,0 @@
-[package]
-name = "reqwest_client"
-version = "0.1.0"
-edition = "2021"
-publish = false
-license = "Apache-2.0"
-
-[lints]
-workspace = true
-
-[features]
-test-support = []
-
-[lib]
-path = "src/reqwest_client.rs"
-doctest = true
-
-[[example]]
-name = "client"
-path = "examples/client.rs"
-
-[dependencies]
-anyhow.workspace = true
-bytes = "1.0"
-futures.workspace = true
-http_client.workspace = true
-serde.workspace = true
-smol.workspace = true
-tokio.workspace = true
-
-reqwest = { workspace = true, features = ["rustls-tls-manual-roots", "stream"] }

crates/reqwest_client/examples/client.rs 🔗

@@ -1,16 +0,0 @@
-use futures::AsyncReadExt as _;
-use http_client::AsyncBody;
-use http_client::HttpClient;
-use reqwest_client::ReqwestClient;
-
-#[tokio::main]
-async fn main() {
-    let resp = ReqwestClient::new()
-        .get("http://zed.dev", AsyncBody::empty(), true)
-        .await
-        .unwrap();
-
-    let mut body = String::new();
-    resp.into_body().read_to_string(&mut body).await.unwrap();
-    println!("{}", &body);
-}

crates/reqwest_client/src/reqwest_client.rs 🔗

@@ -1,259 +0,0 @@
-use std::{any::type_name, borrow::Cow, io::Read, pin::Pin, task::Poll};
-
-use anyhow::anyhow;
-use bytes::{BufMut, Bytes, BytesMut};
-use futures::{AsyncRead, TryStreamExt};
-use http_client::{http, AsyncBody, ReadTimeout};
-use reqwest::header::{HeaderMap, HeaderValue};
-use smol::future::FutureExt;
-
-const DEFAULT_CAPACITY: usize = 4096;
-
-pub struct ReqwestClient {
-    client: reqwest::Client,
-}
-
-impl ReqwestClient {
-    pub fn new() -> Self {
-        Self {
-            client: reqwest::Client::new(),
-        }
-    }
-
-    pub fn user_agent(agent: &str) -> anyhow::Result<Self> {
-        let mut map = HeaderMap::new();
-        map.insert(http::header::USER_AGENT, HeaderValue::from_str(agent)?);
-        Ok(Self {
-            client: reqwest::Client::builder().default_headers(map).build()?,
-        })
-    }
-}
-
-impl From<reqwest::Client> for ReqwestClient {
-    fn from(client: reqwest::Client) -> Self {
-        Self { client }
-    }
-}
-
-// This struct is essentially a re-implementation of
-// https://docs.rs/tokio-util/0.7.12/tokio_util/io/struct.ReaderStream.html
-// except outside of Tokio's aegis
-struct ReaderStream {
-    reader: Option<Pin<Box<dyn futures::AsyncRead + Send + Sync>>>,
-    buf: BytesMut,
-    capacity: usize,
-}
-
-impl ReaderStream {
-    fn new(reader: Pin<Box<dyn futures::AsyncRead + Send + Sync>>) -> Self {
-        Self {
-            reader: Some(reader),
-            buf: BytesMut::new(),
-            capacity: DEFAULT_CAPACITY,
-        }
-    }
-}
-
-impl futures::Stream for ReaderStream {
-    type Item = std::io::Result<Bytes>;
-
-    fn poll_next(
-        mut self: Pin<&mut Self>,
-        cx: &mut std::task::Context<'_>,
-    ) -> Poll<Option<Self::Item>> {
-        let mut this = self.as_mut();
-
-        let mut reader = match this.reader.take() {
-            Some(r) => r,
-            None => return Poll::Ready(None),
-        };
-
-        if this.buf.capacity() == 0 {
-            let capacity = this.capacity;
-            this.buf.reserve(capacity);
-        }
-
-        match poll_read_buf(&mut reader, cx, &mut this.buf) {
-            Poll::Pending => Poll::Pending,
-            Poll::Ready(Err(err)) => {
-                self.reader = None;
-
-                Poll::Ready(Some(Err(err)))
-            }
-            Poll::Ready(Ok(0)) => {
-                self.reader = None;
-                Poll::Ready(None)
-            }
-            Poll::Ready(Ok(_)) => {
-                let chunk = this.buf.split();
-                self.reader = Some(reader);
-                Poll::Ready(Some(Ok(chunk.freeze())))
-            }
-        }
-    }
-}
-
-/// Implementation from https://docs.rs/tokio-util/0.7.12/src/tokio_util/util/poll_buf.rs.html
-/// Specialized for this use case
-pub fn poll_read_buf(
-    io: &mut Pin<Box<dyn futures::AsyncRead + Send + Sync>>,
-    cx: &mut std::task::Context<'_>,
-    buf: &mut BytesMut,
-) -> Poll<std::io::Result<usize>> {
-    if !buf.has_remaining_mut() {
-        return Poll::Ready(Ok(0));
-    }
-
-    let n = {
-        let dst = buf.chunk_mut();
-
-        // Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
-        // transparent wrapper around `[MaybeUninit<u8>]`.
-        let dst = unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>]) };
-        let mut buf = tokio::io::ReadBuf::uninit(dst);
-        let ptr = buf.filled().as_ptr();
-        let unfilled_portion = buf.initialize_unfilled();
-        // SAFETY: Pin projection
-        let io_pin = unsafe { Pin::new_unchecked(io) };
-        std::task::ready!(io_pin.poll_read(cx, unfilled_portion)?);
-
-        // Ensure the pointer does not change from under us
-        assert_eq!(ptr, buf.filled().as_ptr());
-        buf.filled().len()
-    };
-
-    // Safety: This is guaranteed to be the number of initialized (and read)
-    // bytes due to the invariants provided by `ReadBuf::filled`.
-    unsafe {
-        buf.advance_mut(n);
-    }
-
-    Poll::Ready(Ok(n))
-}
-
-enum WrappedBodyInner {
-    None,
-    SyncReader(std::io::Cursor<Cow<'static, [u8]>>),
-    Stream(ReaderStream),
-}
-
-struct WrappedBody(WrappedBodyInner);
-
-impl WrappedBody {
-    fn new(body: AsyncBody) -> Self {
-        match body.0 {
-            http_client::Inner::Empty => Self(WrappedBodyInner::None),
-            http_client::Inner::SyncReader(cursor) => Self(WrappedBodyInner::SyncReader(cursor)),
-            http_client::Inner::AsyncReader(pin) => {
-                Self(WrappedBodyInner::Stream(ReaderStream::new(pin)))
-            }
-        }
-    }
-}
-
-impl futures::stream::Stream for WrappedBody {
-    type Item = Result<Bytes, std::io::Error>;
-
-    fn poll_next(
-        mut self: std::pin::Pin<&mut Self>,
-        cx: &mut std::task::Context<'_>,
-    ) -> std::task::Poll<Option<Self::Item>> {
-        match &mut self.0 {
-            WrappedBodyInner::None => Poll::Ready(None),
-            WrappedBodyInner::SyncReader(cursor) => {
-                let mut buf = Vec::new();
-                match cursor.read_to_end(&mut buf) {
-                    Ok(bytes) => {
-                        if bytes == 0 {
-                            return Poll::Ready(None);
-                        } else {
-                            return Poll::Ready(Some(Ok(Bytes::from(buf))));
-                        }
-                    }
-                    Err(e) => return Poll::Ready(Some(Err(e))),
-                }
-            }
-            WrappedBodyInner::Stream(stream) => {
-                // SAFETY: Pin projection
-                let stream = unsafe { Pin::new_unchecked(stream) };
-                futures::Stream::poll_next(stream, cx)
-            }
-        }
-    }
-}
-
-impl http_client::HttpClient for ReqwestClient {
-    fn proxy(&self) -> Option<&http::Uri> {
-        None
-    }
-
-    fn type_name(&self) -> &'static str {
-        type_name::<Self>()
-    }
-
-    fn send(
-        &self,
-        req: http::Request<http_client::AsyncBody>,
-    ) -> futures::future::BoxFuture<
-        'static,
-        Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>,
-    > {
-        let (parts, body) = req.into_parts();
-
-        let mut request = self.client.request(parts.method, parts.uri.to_string());
-
-        request = request.headers(parts.headers);
-
-        if let Some(redirect_policy) = parts.extensions.get::<http_client::RedirectPolicy>() {
-            request = request.redirect_policy(match redirect_policy {
-                http_client::RedirectPolicy::NoFollow => reqwest::redirect::Policy::none(),
-                http_client::RedirectPolicy::FollowLimit(limit) => {
-                    reqwest::redirect::Policy::limited(*limit as usize)
-                }
-                http_client::RedirectPolicy::FollowAll => reqwest::redirect::Policy::limited(100),
-            });
-        }
-
-        if let Some(ReadTimeout(timeout)) = parts.extensions.get::<ReadTimeout>() {
-            request = request.timeout(*timeout);
-        }
-
-        let body = WrappedBody::new(body);
-        let request = request.body(reqwest::Body::wrap_stream(body));
-
-        async move {
-            let response = request.send().await.map_err(|e| anyhow!(e))?;
-            let status = response.status();
-            let mut builder = http::Response::builder().status(status.as_u16());
-            for (name, value) in response.headers() {
-                builder = builder.header(name, value);
-            }
-            let bytes = response.bytes_stream();
-            let bytes = bytes
-                .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e))
-                .into_async_read();
-            let body = http_client::AsyncBody::from_reader(bytes);
-            builder.body(body).map_err(|e| anyhow!(e))
-        }
-        .boxed()
-    }
-}
-
-#[cfg(test)]
-mod test {
-
-    use core::str;
-
-    use http_client::AsyncBody;
-    use smol::stream::StreamExt;
-
-    use crate::WrappedBody;
-
-    #[tokio::test]
-    async fn test_sync_streaming_upload() {
-        let mut body = WrappedBody::new(AsyncBody::from("hello there".to_string())).fuse();
-        let result = body.next().await.unwrap().unwrap();
-        assert!(body.next().await.is_none());
-        assert_eq!(str::from_utf8(&result).unwrap(), "hello there");
-    }
-}

crates/semantic_index/Cargo.toml 🔗

@@ -51,6 +51,7 @@ workspace.workspace = true
 worktree.workspace = true
 
 [dev-dependencies]
+isahc_http_client.workspace = true
 client = { workspace = true, features = ["test-support"] }
 env_logger.workspace = true
 fs = { workspace = true, features = ["test-support"] }
@@ -61,7 +62,6 @@ language = { workspace = true, features = ["test-support"] }
 languages.workspace = true
 project = { workspace = true, features = ["test-support"] }
 tempfile.workspace = true
-ureq_client.workspace = true
 util = { workspace = true, features = ["test-support"] }
 workspace = { workspace = true, features = ["test-support"] }
 worktree = { workspace = true, features = ["test-support"] }

crates/semantic_index/examples/index.rs 🔗

@@ -2,6 +2,7 @@ use client::Client;
 use futures::channel::oneshot;
 use gpui::App;
 use http_client::HttpClientWithUrl;
+use isahc_http_client::IsahcHttpClient;
 use language::language_settings::AllLanguageSettings;
 use project::Project;
 use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, SemanticDb};
@@ -28,11 +29,7 @@ fn main() {
         let clock = Arc::new(FakeSystemClock::default());
 
         let http = Arc::new(HttpClientWithUrl::new(
-            Arc::new(ureq_client::UreqClient::new(
-                None,
-                "Zed semantic index example".to_string(),
-                cx.background_executor().clone(),
-            )),
+            IsahcHttpClient::new(None, None),
             "http://localhost:11434",
             None,
         ));

crates/storybook/Cargo.toml 🔗

@@ -22,6 +22,7 @@ editor.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
 indoc.workspace = true
+isahc_http_client.workspace = true
 language.workspace = true
 log.workspace = true
 menu.workspace = true
@@ -35,7 +36,6 @@ strum = { workspace = true, features = ["derive"] }
 theme.workspace = true
 title_bar = { workspace = true, features = ["stories"] }
 ui = { workspace = true, features = ["stories"] }
-ureq_client.workspace = true
 
 [dev-dependencies]
 gpui = { workspace = true, features = ["test-support"] }

crates/storybook/src/storybook.rs 🔗

@@ -4,14 +4,13 @@ mod assets;
 mod stories;
 mod story_selector;
 
-use std::sync::Arc;
-
 use clap::Parser;
 use dialoguer::FuzzySelect;
 use gpui::{
     div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowBounds,
     WindowOptions,
 };
+use isahc_http_client::IsahcHttpClient;
 use log::LevelFilter;
 use project::Project;
 use settings::{KeymapFile, Settings};
@@ -19,7 +18,6 @@ use simplelog::SimpleLogger;
 use strum::IntoEnumIterator;
 use theme::{ThemeRegistry, ThemeSettings};
 use ui::prelude::*;
-use ureq_client::UreqClient;
 
 use crate::app_menus::app_menus;
 use crate::assets::Assets;
@@ -68,12 +66,8 @@ fn main() {
     gpui::App::new().with_assets(Assets).run(move |cx| {
         load_embedded_fonts(cx).unwrap();
 
-        let http_client = UreqClient::new(
-            None,
-            "zed_storybook".to_string(),
-            cx.background_executor().clone(),
-        );
-        cx.set_http_client(Arc::new(http_client));
+        let http_client = IsahcHttpClient::new(None, Some("zed_storybook".to_string()));
+        cx.set_http_client(http_client);
 
         settings::init(cx);
         theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);

crates/ureq_client/examples/client.rs 🔗

@@ -1,24 +0,0 @@
-use futures::AsyncReadExt;
-use http_client::{AsyncBody, HttpClient};
-use ureq_client::UreqClient;
-
-fn main() {
-    gpui::App::headless().run(|cx| {
-        println!("{:?}", std::thread::current().id());
-        cx.spawn(|cx| async move {
-            let resp = UreqClient::new(
-                None,
-                "Conrad's bot".to_string(),
-                cx.background_executor().clone(),
-            )
-            .get("http://zed.dev", AsyncBody::empty(), true)
-            .await
-            .unwrap();
-
-            let mut body = String::new();
-            resp.into_body().read_to_string(&mut body).await.unwrap();
-            println!("{}", body);
-        })
-        .detach();
-    })
-}

crates/ureq_client/src/ureq_client.rs 🔗

@@ -1,200 +0,0 @@
-use std::any::type_name;
-use std::collections::HashMap;
-use std::io::Read;
-use std::sync::Arc;
-use std::time::Duration;
-use std::{pin::Pin, task::Poll};
-
-use anyhow::Error;
-use futures::channel::mpsc;
-use futures::future::BoxFuture;
-use futures::{AsyncRead, SinkExt, StreamExt};
-use http_client::{http, AsyncBody, HttpClient, RedirectPolicy, Uri};
-use smol::future::FutureExt;
-use util::ResultExt;
-
-pub struct UreqClient {
-    // Note in ureq 2.x the options are stored on the Agent.
-    // In ureq 3.x we'll be able to set these on the request.
-    // In practice it's probably "fine" to have many clients, the number of distinct options
-    // is low; and most requests to the same connection will have the same options so the
-    // connection pool will work.
-    clients: Arc<parking_lot::Mutex<HashMap<(Duration, RedirectPolicy), ureq::Agent>>>,
-    proxy_url: Option<Uri>,
-    proxy: Option<ureq::Proxy>,
-    user_agent: String,
-    background_executor: gpui::BackgroundExecutor,
-}
-
-impl UreqClient {
-    pub fn new(
-        proxy_url: Option<Uri>,
-        user_agent: String,
-        background_executor: gpui::BackgroundExecutor,
-    ) -> Self {
-        Self {
-            clients: Arc::default(),
-            proxy_url: proxy_url.clone(),
-            proxy: proxy_url.and_then(|url| ureq::Proxy::new(url.to_string()).log_err()),
-            user_agent,
-            background_executor,
-        }
-    }
-
-    fn agent_for(&self, redirect_policy: RedirectPolicy, timeout: Duration) -> ureq::Agent {
-        let mut clients = self.clients.lock();
-        // in case our assumption of distinct options is wrong, we'll sporadically clean it out.
-        if clients.len() > 50 {
-            clients.clear()
-        }
-
-        clients
-            .entry((timeout, redirect_policy.clone()))
-            .or_insert_with(|| {
-                let mut builder = ureq::AgentBuilder::new()
-                    .timeout_connect(Duration::from_secs(5))
-                    .timeout_read(timeout)
-                    .timeout_write(timeout)
-                    .user_agent(&self.user_agent)
-                    .tls_config(http_client::TLS_CONFIG.clone())
-                    .redirects(match redirect_policy {
-                        RedirectPolicy::NoFollow => 0,
-                        RedirectPolicy::FollowLimit(limit) => limit,
-                        RedirectPolicy::FollowAll => 100,
-                    });
-                if let Some(proxy) = &self.proxy {
-                    builder = builder.proxy(proxy.clone());
-                }
-                builder.build()
-            })
-            .clone()
-    }
-}
-impl HttpClient for UreqClient {
-    fn proxy(&self) -> Option<&Uri> {
-        self.proxy_url.as_ref()
-    }
-
-    fn type_name(&self) -> &'static str {
-        type_name::<Self>()
-    }
-
-    fn send(
-        &self,
-        request: http::Request<AsyncBody>,
-    ) -> BoxFuture<'static, Result<http::Response<AsyncBody>, Error>> {
-        let agent = self.agent_for(
-            request
-                .extensions()
-                .get::<RedirectPolicy>()
-                .cloned()
-                .unwrap_or_default(),
-            request
-                .extensions()
-                .get::<http_client::ReadTimeout>()
-                .cloned()
-                .unwrap_or_default()
-                .0,
-        );
-        let mut req = agent.request(&request.method().as_ref(), &request.uri().to_string());
-        for (name, value) in request.headers().into_iter() {
-            req = req.set(name.as_str(), value.to_str().unwrap());
-        }
-        let body = request.into_body();
-        let executor = self.background_executor.clone();
-
-        self.background_executor
-            .spawn(async move {
-                let response = match req.send(body) {
-                    Ok(response) => response,
-                    Err(e) => match e {
-                        ureq::Error::Status(_, response) => response,
-                        ureq::Error::Transport(transport) => {
-                            anyhow::bail!(transport)
-                        }
-                    },
-                };
-
-                let mut builder = http::Response::builder()
-                    .status(response.status())
-                    .version(http::Version::HTTP_11);
-                for name in response.headers_names() {
-                    if let Some(value) = response.header(&name) {
-                        builder = builder.header(name, value);
-                    }
-                }
-
-                let body = AsyncBody::from_reader(UreqResponseReader::new(executor, response));
-                let http_response = builder.body(body)?;
-
-                Ok(http_response)
-            })
-            .boxed()
-    }
-}
-
-struct UreqResponseReader {
-    receiver: mpsc::Receiver<std::io::Result<Vec<u8>>>,
-    buffer: Vec<u8>,
-    idx: usize,
-    _task: gpui::Task<()>,
-}
-
-impl UreqResponseReader {
-    fn new(background_executor: gpui::BackgroundExecutor, response: ureq::Response) -> Self {
-        let (mut sender, receiver) = mpsc::channel(1);
-        let mut reader = response.into_reader();
-        let task = background_executor.spawn(async move {
-            let mut buffer = vec![0; 8192];
-            loop {
-                let n = match reader.read(&mut buffer) {
-                    Ok(0) => break,
-                    Ok(n) => n,
-                    Err(e) => {
-                        let _ = sender.send(Err(e)).await;
-                        break;
-                    }
-                };
-                let _ = sender.send(Ok(buffer[..n].to_vec())).await;
-            }
-        });
-
-        UreqResponseReader {
-            _task: task,
-            receiver,
-            buffer: Vec::new(),
-            idx: 0,
-        }
-    }
-}
-
-impl AsyncRead for UreqResponseReader {
-    fn poll_read(
-        mut self: Pin<&mut Self>,
-        cx: &mut std::task::Context<'_>,
-        buf: &mut [u8],
-    ) -> Poll<std::io::Result<usize>> {
-        if self.buffer.is_empty() {
-            match self.receiver.poll_next_unpin(cx) {
-                Poll::Ready(Some(Ok(data))) => self.buffer = data,
-                Poll::Ready(Some(Err(e))) => {
-                    return Poll::Ready(Err(e));
-                }
-                Poll::Ready(None) => {
-                    return Poll::Ready(Ok(0));
-                }
-                Poll::Pending => {
-                    return Poll::Pending;
-                }
-            }
-        }
-        let n = std::cmp::min(buf.len(), self.buffer.len() - self.idx);
-        buf[..n].copy_from_slice(&self.buffer[self.idx..self.idx + n]);
-        self.idx += n;
-        if self.idx == self.buffer.len() {
-            self.buffer.clear();
-            self.idx = 0;
-        }
-        Poll::Ready(Ok(n))
-    }
-}

crates/vim/Cargo.toml 🔗

@@ -17,7 +17,7 @@ neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"]
 
 [dependencies]
 anyhow.workspace = true
-async-compat = { workspace = true, "optional" = true }
+async-compat = { version = "0.2.1", "optional" = true }
 async-trait = { workspace = true, "optional" = true }
 collections.workspace = true
 command_palette.workspace = true

crates/zed/Cargo.toml 🔗

@@ -57,6 +57,7 @@ http_client.workspace = true
 image_viewer.workspace = true
 inline_completion_button.workspace = true
 install_cli.workspace = true
+isahc_http_client.workspace = true
 journal.workspace = true
 language.workspace = true
 language_model.workspace = true
@@ -107,7 +108,6 @@ theme.workspace = true
 theme_selector.workspace = true
 time.workspace = true
 ui.workspace = true
-ureq_client.workspace = true
 url.workspace = true
 urlencoding = "2.1.2"
 util.workspace = true

crates/zed/src/main.rs 🔗

@@ -24,9 +24,9 @@ use gpui::{
     UpdateGlobal as _, VisualContext,
 };
 use http_client::{read_proxy_from_env, Uri};
+use isahc_http_client::IsahcHttpClient;
 use language::LanguageRegistry;
 use log::LevelFilter;
-use ureq_client::UreqClient;
 
 use assets::Assets;
 use node_runtime::{NodeBinaryOptions, NodeRuntime};
@@ -334,7 +334,9 @@ fn main() {
 
     log::info!("========== starting zed ==========");
 
-    let app = App::new().with_assets(Assets);
+    let app = App::new()
+        .with_assets(Assets)
+        .with_http_client(IsahcHttpClient::new(None, None));
 
     let system_id = app.background_executor().block(system_id()).ok();
     let installation_id = app.background_executor().block(installation_id()).ok();
@@ -468,8 +470,8 @@ fn main() {
                     .ok()
             })
             .or_else(read_proxy_from_env);
-        let http = UreqClient::new(proxy_url, user_agent, cx.background_executor().clone());
-        cx.set_http_client(Arc::new(http));
+        let http = IsahcHttpClient::new(proxy_url, Some(user_agent));
+        cx.set_http_client(http);
 
         <dyn Fs>::set_global(fs.clone(), cx);