From e0f2e5ee1199c489fa60525dae0607bf1524524f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 02:17:05 +0000 Subject: [PATCH 1/2] feat(rust): add Vertex auth base provider Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com> --- litellm-rust/Cargo.lock | 654 +++++++++++++++++- litellm-rust/crates/core/Cargo.toml | 2 + .../core/src/providers/vertex_ai/constants.rs | 7 + .../core/src/providers/vertex_ai/mod.rs | 4 + .../src/providers/vertex_ai/vertex_base.rs | 368 ++++++++++ 5 files changed, 1013 insertions(+), 22 deletions(-) create mode 100644 litellm-rust/crates/core/src/providers/vertex_ai/constants.rs create mode 100644 litellm-rust/crates/core/src/providers/vertex_ai/vertex_base.rs diff --git a/litellm-rust/Cargo.lock b/litellm-rust/Cargo.lock index 402d16715a3..502f8edb4ba 100644 --- a/litellm-rust/Cargo.lock +++ b/litellm-rust/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arc-swap" version = "1.9.2" @@ -19,7 +28,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -313,7 +322,7 @@ checksum = "221eaa237ddf1ca79b60d1372aad77e47f9c0ea5b3ce5099da8c61d027dc77b3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -478,6 +487,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -495,6 +513,9 @@ name = "bytes" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +dependencies = [ + "serde", +] [[package]] name = "bytes-utils" @@ -530,6 +551,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "cmake" version = "0.1.58" @@ -545,6 +589,16 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "const-oid" version = "0.10.2" @@ -613,6 +667,40 @@ dependencies = [ "cmov", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.118", +] + [[package]] name = "data-encoding" version = "2.11.0" @@ -624,6 +712,9 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] [[package]] name = "digest" @@ -655,7 +746,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -664,6 +755,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.16.0" @@ -676,6 +773,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -709,6 +816,20 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -739,7 +860,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -816,6 +937,82 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", +] + +[[package]] +name = "google-cloud-auth" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4595b69c95c727ac896a4f2f16d0cdaa6f547de0bc5d64dbe9201487fc3226d5" +dependencies = [ + "async-trait", + "base64", + "bytes", + "chrono", + "google-cloud-gax", + "hex", + "hmac", + "http 1.4.2", + "reqwest 0.13.4", + "rustc_version", + "rustls 0.23.41", + "rustls-pki-types", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "time", + "tokio", + "url", +] + +[[package]] +name = "google-cloud-gax" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3103a4a9013f1aed573ca56e19a9680b0211643a99ea85caf524b397d6be8be3" +dependencies = [ + "bytes", + "futures", + "google-cloud-rpc", + "google-cloud-wkt", + "http 1.4.2", + "pin-project", + "rand 0.10.2", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "google-cloud-rpc" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2162c08a89118130979ba261080e960e44cdcb2d6e2ab8ca9b1da245285d353" +dependencies = [ + "bytes", + "google-cloud-wkt", + "serde", + "serde_json", + "serde_with", +] + +[[package]] +name = "google-cloud-wkt" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46df1fcc3ab69164af3f4199ed21f45b5dbc56d9f03211eb4fa20116d442364b" +dependencies = [ + "base64", + "bytes", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "time", + "url", ] [[package]] @@ -830,7 +1027,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -849,13 +1046,19 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.2", - "indexmap", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.17.1" @@ -1060,6 +1263,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -1142,6 +1369,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1163,6 +1396,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1170,7 +1414,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", + "serde", + "serde_core", ] [[package]] @@ -1185,6 +1431,55 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.118", +] + +[[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.118", +] + [[package]] name = "jobserver" version = "0.1.35" @@ -1222,7 +1517,7 @@ dependencies = [ "futures-util", "litellm-core", "pyo3", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -1242,8 +1537,9 @@ dependencies = [ "aws-sigv4", "aws-smithy-runtime-api", "aws-types", + "google-cloud-auth", "rand 0.8.6", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -1358,6 +1654,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1471,7 +1787,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1483,7 +1799,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1512,6 +1828,7 @@ version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -1583,6 +1900,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -1621,6 +1949,32 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.0", +] + [[package]] name = "regex-lite" version = "0.1.9" @@ -1670,6 +2024,44 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http 1.4.2", + "http-body 1.0.1", + "http-body-util", + "hyper 1.10.1", + "hyper-rustls 0.27.9", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.41", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -1718,6 +2110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -1748,6 +2141,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.41", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.13", + "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.101.7" @@ -1782,6 +2202,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -1791,6 +2220,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "sct" version = "0.7.1" @@ -1857,7 +2310,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1896,6 +2349,38 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1935,6 +2420,32 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -1973,6 +2484,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -1990,6 +2507,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fac314a64dc9a36e61a9eb4261a5e9bbfbc922b27e518af97bc32b926cf967" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -2007,7 +2535,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2042,7 +2570,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2053,7 +2581,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2121,6 +2649,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", @@ -2134,7 +2663,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2252,7 +2781,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2360,6 +2889,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2426,7 +2965,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -2472,6 +3011,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.8" @@ -2481,12 +3029,74 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2680,7 +3290,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -2701,7 +3311,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2721,7 +3331,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -2761,7 +3371,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] diff --git a/litellm-rust/crates/core/Cargo.toml b/litellm-rust/crates/core/Cargo.toml index 65c6db7412c..f72497d4d9f 100644 --- a/litellm-rust/crates/core/Cargo.toml +++ b/litellm-rust/crates/core/Cargo.toml @@ -17,6 +17,7 @@ aws-sdk-sts = { version = "1.108.0", default-features = false, features = ["rust aws-sigv4 = { version = "1.5.1", optional = true } aws-types = { version = "1.4.0", optional = true } aws-smithy-runtime-api = { version = "1.13.0", optional = true } +google-cloud-auth = { version = "=1.12.0", default-features = false, features = ["default-rustls-provider"], optional = true } [features] default = [] @@ -28,6 +29,7 @@ bedrock-auth = [ "dep:aws-types", "dep:aws-smithy-runtime-api", ] +vertex-auth = ["dep:google-cloud-auth"] [dev-dependencies] reqwest.workspace = true diff --git a/litellm-rust/crates/core/src/providers/vertex_ai/constants.rs b/litellm-rust/crates/core/src/providers/vertex_ai/constants.rs new file mode 100644 index 00000000000..846d54213f8 --- /dev/null +++ b/litellm-rust/crates/core/src/providers/vertex_ai/constants.rs @@ -0,0 +1,7 @@ +pub const VERTEXAI_PROJECT: &str = "VERTEXAI_PROJECT"; +pub const VERTEXAI_LOCATION: &str = "VERTEXAI_LOCATION"; +pub const VERTEXAI_CREDENTIALS: &str = "VERTEXAI_CREDENTIALS"; +pub const GOOGLE_APPLICATION_CREDENTIALS: &str = "GOOGLE_APPLICATION_CREDENTIALS"; +pub const GOOGLE_CLOUD_PROJECT: &str = "GOOGLE_CLOUD_PROJECT"; +pub const GOOGLE_CLOUD_QUOTA_PROJECT: &str = "GOOGLE_CLOUD_QUOTA_PROJECT"; +pub const CLOUD_PLATFORM_SCOPE: &str = "https://www.googleapis.com/auth/cloud-platform"; diff --git a/litellm-rust/crates/core/src/providers/vertex_ai/mod.rs b/litellm-rust/crates/core/src/providers/vertex_ai/mod.rs index 3621ff6a2fd..424eaab91cd 100644 --- a/litellm-rust/crates/core/src/providers/vertex_ai/mod.rs +++ b/litellm-rust/crates/core/src/providers/vertex_ai/mod.rs @@ -1 +1,5 @@ +#[cfg(feature = "vertex-auth")] +mod constants; pub mod ocr; +#[cfg(feature = "vertex-auth")] +pub mod vertex_base; diff --git a/litellm-rust/crates/core/src/providers/vertex_ai/vertex_base.rs b/litellm-rust/crates/core/src/providers/vertex_ai/vertex_base.rs new file mode 100644 index 00000000000..8909b2bc414 --- /dev/null +++ b/litellm-rust/crates/core/src/providers/vertex_ai/vertex_base.rs @@ -0,0 +1,368 @@ +//! User-directed exception: this base provider owns Google auth I/O for parity +//! with Python's `VertexBase`; the broader core purity guidance is reconciled +//! separately. + +use std::env; +use std::fs; +use std::path::Path; +use std::sync::{Mutex, OnceLock}; + +use google_cloud_auth::credentials::external_account; +use google_cloud_auth::credentials::service_account; +use google_cloud_auth::credentials::user_account; +use google_cloud_auth::credentials::AccessTokenCredentials; +use serde_json::Value; +use sha2::{Digest, Sha256}; + +use crate::caching::in_memory_cache::InMemoryCache; +use crate::error::{CoreError, CoreResult}; + +use super::constants::{ + CLOUD_PLATFORM_SCOPE, GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_CLOUD_PROJECT, + GOOGLE_CLOUD_QUOTA_PROJECT, VERTEXAI_CREDENTIALS, VERTEXAI_LOCATION, VERTEXAI_PROJECT, +}; + +static VERTEX_CREDENTIALS_CACHE: OnceLock>> = + OnceLock::new(); + +// google-cloud-auth owns async token caching and refresh; Python's single-flight machinery is intentionally not ported. + +#[derive(Clone, Debug, PartialEq)] +pub enum VertexCredentialsInput { + Json(Value), + String(String), +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct VertexAuthConfig { + pub credentials: Option, + pub location: Option, + pub project_id: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum VertexAuthSource { + AwsWorkloadIdentity, + ExecutableWorkloadIdentity, + IdentityPoolWorkloadIdentity, + AuthorizedUser, + ServiceAccount, + DefaultAdc, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VertexToken { + pub access_token: String, + pub project_id: String, +} + +impl VertexAuthConfig { + pub fn from_environment() -> Self { + Self { + credentials: non_empty_env(VERTEXAI_CREDENTIALS) + .or_else(|| non_empty_env(GOOGLE_APPLICATION_CREDENTIALS)) + .map(VertexCredentialsInput::String), + location: non_empty_env(VERTEXAI_LOCATION), + project_id: non_empty_env(VERTEXAI_PROJECT) + .or_else(|| non_empty_env(GOOGLE_CLOUD_PROJECT)), + } + } +} + +pub fn resolve_credentials_input( + credentials: Option, +) -> CoreResult> { + let Some(credentials) = credentials else { + return Ok(None); + }; + + let value = match credentials { + VertexCredentialsInput::Json(value) => value, + VertexCredentialsInput::String(value) => { + if value.trim().is_empty() { + return Ok(None); + } + let path = Path::new(&value); + let json = if path.is_file() { + fs::read_to_string(path).map_err(|error| { + CoreError::Auth(format!("unable to read Vertex credentials file: {error}")) + })? + } else { + value + }; + serde_json::from_str(&json).map_err(|error| { + CoreError::Auth(format!("invalid Vertex credentials JSON: {error}")) + })? + } + }; + + if value.is_object() { + Ok(Some(value)) + } else { + Err(CoreError::InvalidType { + expected: "object", + actual: crate::error::json_type_name(&value), + }) + } +} + +pub fn classify_auth(credentials: Option<&Value>) -> VertexAuthSource { + let Some(credentials) = credentials else { + return VertexAuthSource::DefaultAdc; + }; + + let credential_type = credentials.get("type").and_then(Value::as_str); + if credential_type == Some("external_account") { + let source = credentials + .get("credential_source") + .and_then(Value::as_object); + let environment_id = source + .and_then(|source| source.get("environment_id")) + .and_then(Value::as_str) + .unwrap_or_default(); + if environment_id.contains("aws") { + return VertexAuthSource::AwsWorkloadIdentity; + } + if source.is_some_and(|source| source.contains_key("executable")) { + return VertexAuthSource::ExecutableWorkloadIdentity; + } + return VertexAuthSource::IdentityPoolWorkloadIdentity; + } + if credential_type == Some("authorized_user") { + return VertexAuthSource::AuthorizedUser; + } + VertexAuthSource::ServiceAccount +} + +pub async fn resolve_token(config: VertexAuthConfig) -> CoreResult { + let credentials = resolve_credentials_input(config.credentials)?; + let source = classify_auth(credentials.as_ref()); + let project_id = project_id(config.project_id, credentials.as_ref())?; + let cache_key = cache_key(credentials.as_ref(), &project_id); + let provider = cached_credentials(&cache_key)?; + let provider = match provider { + Some(provider) => provider, + None => { + let provider = build_credentials(source, credentials)?; + let access_token = provider.access_token().await.map_err(|error| { + CoreError::Auth(format!("Google token resolution failed: {error}")) + })?; + store_credentials(cache_key, provider.clone())?; + return Ok(VertexToken { + access_token: access_token.token, + project_id, + }); + } + }; + let access_token = provider + .access_token() + .await + .map_err(|error| CoreError::Auth(format!("Google token resolution failed: {error}")))?; + Ok(VertexToken { + access_token: access_token.token, + project_id, + }) +} + +fn build_credentials( + source: VertexAuthSource, + credentials: Option, +) -> CoreResult { + let scopes = [CLOUD_PLATFORM_SCOPE]; + match source { + VertexAuthSource::DefaultAdc => google_cloud_auth::credentials::Builder::default() + .with_scopes(scopes) + .build_access_token_credentials() + .map_err(|error| CoreError::Auth(format!("Google ADC resolution failed: {error}"))), + VertexAuthSource::AwsWorkloadIdentity + | VertexAuthSource::ExecutableWorkloadIdentity + | VertexAuthSource::IdentityPoolWorkloadIdentity => { + let value = credentials.ok_or(CoreError::MissingField("credentials"))?; + external_account::Builder::new(value) + .with_scopes(scopes) + .build_access_token_credentials() + .map_err(|error| { + CoreError::Auth(format!( + "Google external account resolution failed: {error}" + )) + }) + } + VertexAuthSource::AuthorizedUser => { + let value = credentials.ok_or(CoreError::MissingField("credentials"))?; + user_account::Builder::new(value) + .with_scopes(scopes) + .build_access_token_credentials() + .map_err(|error| { + CoreError::Auth(format!("Google authorized-user resolution failed: {error}")) + }) + } + VertexAuthSource::ServiceAccount => { + let value = credentials.ok_or(CoreError::MissingField("credentials"))?; + service_account::Builder::new(value) + .with_access_specifier(service_account::AccessSpecifier::from_scopes(scopes)) + .build_access_token_credentials() + .map_err(|error| { + CoreError::Auth(format!("Google service-account resolution failed: {error}")) + }) + } + } +} + +fn project_id(explicit: Option, credentials: Option<&Value>) -> CoreResult { + let project_id = explicit + .filter(|value| !value.trim().is_empty()) + .or_else(|| { + credentials.and_then(|value| { + value + .get("project_id") + .and_then(Value::as_str) + .map(str::to_owned) + }) + }) + .or_else(|| { + credentials.and_then(|value| { + value + .get("quota_project_id") + .and_then(Value::as_str) + .map(str::to_owned) + }) + }) + .or_else(|| non_empty_env(GOOGLE_CLOUD_PROJECT)) + .or_else(|| non_empty_env(GOOGLE_CLOUD_QUOTA_PROJECT)); + project_id.ok_or(CoreError::MissingField("project_id")) +} + +fn cache_key(credentials: Option<&Value>, project_id: &str) -> String { + let mut hasher = Sha256::new(); + if let Some(credentials) = credentials { + hasher.update(credentials.to_string()); + } + hasher.update(project_id); + format!("{:x}", hasher.finalize()) +} + +fn cached_credentials(key: &str) -> CoreResult> { + let cache = VERTEX_CREDENTIALS_CACHE.get_or_init(|| Mutex::new(InMemoryCache::default())); + let mut cache = cache + .lock() + .map_err(|_| CoreError::Auth("Vertex credential cache lock poisoned".to_string()))?; + Ok(cache.get_cache(key)) +} + +fn store_credentials(key: String, credentials: AccessTokenCredentials) -> CoreResult<()> { + let cache = VERTEX_CREDENTIALS_CACHE.get_or_init(|| Mutex::new(InMemoryCache::default())); + let mut cache = cache + .lock() + .map_err(|_| CoreError::Auth("Vertex credential cache lock poisoned".to_string()))?; + cache.set_cache(key, credentials, None); + Ok(()) +} + +fn non_empty_env(name: &str) -> Option { + env::var(name).ok().filter(|value| !value.trim().is_empty()) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn classifier_preserves_external_account_dispatch_order() { + assert_eq!( + classify_auth(Some(&json!({ + "type": "external_account", + "credential_source": {"environment_id": "aws1", "executable": {}} + }))), + VertexAuthSource::AwsWorkloadIdentity + ); + assert_eq!( + classify_auth(Some(&json!({ + "type": "external_account", + "credential_source": {"executable": {"command": "token"}} + }))), + VertexAuthSource::ExecutableWorkloadIdentity + ); + assert_eq!( + classify_auth(Some( + &json!({"type": "external_account", "credential_source": {}}) + )), + VertexAuthSource::IdentityPoolWorkloadIdentity + ); + } + + #[test] + fn classifier_covers_authorized_service_and_default_sources() { + assert_eq!( + classify_auth(Some(&json!({"type": "authorized_user"}))), + VertexAuthSource::AuthorizedUser + ); + assert_eq!( + classify_auth(Some(&json!({"type": "service_account"}))), + VertexAuthSource::ServiceAccount + ); + assert_eq!( + classify_auth(Some(&json!({"client_email": "test@example.com"}))), + VertexAuthSource::ServiceAccount + ); + assert_eq!(classify_auth(None), VertexAuthSource::DefaultAdc); + } + + #[test] + fn credential_input_reads_files_inline_json_and_objects() { + let path = std::env::temp_dir().join(format!("vertex-auth-{}.json", std::process::id())); + std::fs::write(&path, r#"{"type":"authorized_user"}"#).unwrap(); + assert_eq!( + resolve_credentials_input(Some(VertexCredentialsInput::String( + path.to_string_lossy().into_owned() + ))) + .unwrap(), + Some(json!({"type": "authorized_user"})) + ); + std::fs::remove_file(path).unwrap(); + assert_eq!( + resolve_credentials_input(Some(VertexCredentialsInput::String( + r#"{"type":"service_account"}"#.to_string() + ))) + .unwrap(), + Some(json!({"type": "service_account"})) + ); + assert_eq!( + resolve_credentials_input(Some(VertexCredentialsInput::Json(json!({ + "type": "external_account" + })))) + .unwrap(), + Some(json!({"type": "external_account"})) + ); + } + + #[test] + fn credential_input_treats_empty_as_absent_and_rejects_invalid_json() { + assert_eq!( + resolve_credentials_input(Some(VertexCredentialsInput::String(" \n".to_string()))) + .unwrap(), + None + ); + assert!(matches!( + resolve_credentials_input(Some(VertexCredentialsInput::String("{".to_string()))), + Err(CoreError::Auth(_)) + )); + } + + #[test] + fn project_id_explicit_value_wins_over_credential_and_environment() { + let credentials = json!({"project_id": "credential-project"}); + assert_eq!( + project_id(Some("explicit-project".to_string()), Some(&credentials)).unwrap(), + "explicit-project" + ); + assert_eq!( + project_id(None, Some(&credentials)).unwrap(), + "credential-project" + ); + assert_eq!( + project_id(None, Some(&json!({"quota_project_id": "quota-project"}))).unwrap(), + "quota-project" + ); + } +} From 0ca7557ca37433c64180c5a7d25f9dce7dc7b5e8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 02:39:23 +0000 Subject: [PATCH 2/2] ci(rust): cover Vertex auth feature Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com> --- .github/workflows/test-rust.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/test-rust.yml b/.github/workflows/test-rust.yml index 21e1bcb90c6..3a2c550443d 100644 --- a/.github/workflows/test-rust.yml +++ b/.github/workflows/test-rust.yml @@ -64,8 +64,14 @@ jobs: - name: Run Clippy with Bedrock auth run: cargo clippy -p litellm-core --all-targets --features bedrock-auth --locked -- -D warnings + - name: Run Clippy with Vertex auth + run: cargo clippy -p litellm-core --all-targets --features vertex-auth --locked -- -D warnings + - name: Run Rust tests run: cargo test --workspace --locked - name: Run core tests with Bedrock auth run: cargo test -p litellm-core --features bedrock-auth --locked + + - name: Run core tests with Vertex auth + run: cargo test -p litellm-core --features vertex-auth --locked